Merge branch 'main' into feat/level
This commit is contained in:
commit
2ec452b883
56 files changed
+509
-1318
No files matched your search
@@ -1,8 +1,6 @@
|
||||
package admin
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/qianfree/team-api/internal/service"
|
||||
)
|
||||
|
||||
@@ -17,16 +15,3 @@ func New() *sAdmin {
|
||||
func init() {
|
||||
service.RegisterAdmin(New())
|
||||
}
|
||||
|
||||
// getCtxUserID extracts the admin user ID from context.
|
||||
// The key "userId" is set by admin_auth middleware.
|
||||
func getCtxUserID(ctx context.Context) int64 {
|
||||
val := ctx.Value("userId")
|
||||
if val == nil {
|
||||
return 0
|
||||
}
|
||||
if id, ok := val.(int64); ok {
|
||||
return id
|
||||
}
|
||||
return 0
|
||||
}
|
||||
@@ -6,7 +6,6 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/gogf/gf/v2/database/gdb"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/os/gtime"
|
||||
|
||||
v1 "github.com/qianfree/team-api/api/admin/v1"
|
||||
@@ -191,7 +190,7 @@ func (s *sAdmin) UpdateUser(ctx context.Context, req *v1.AdminUserUpdateReq) (*v
|
||||
|
||||
// DeleteUser deletes an admin user.
|
||||
func (s *sAdmin) DeleteUser(ctx context.Context, req *v1.AdminUserDeleteReq) (*v1.AdminUserDeleteRes, error) {
|
||||
currentUserID := ctxUserID(ctx)
|
||||
currentUserID := common.GetCtxUserID(ctx)
|
||||
|
||||
if req.Id == currentUserID {
|
||||
return nil, common.NewBadRequestError("不能删除当前登录的用户")
|
||||
@@ -227,7 +226,7 @@ func (s *sAdmin) DeleteUser(ctx context.Context, req *v1.AdminUserDeleteReq) (*v
|
||||
|
||||
// UpdateUserStatus enables or disables an admin user.
|
||||
func (s *sAdmin) UpdateUserStatus(ctx context.Context, req *v1.AdminUserUpdateStatusReq) (*v1.AdminUserUpdateStatusRes, error) {
|
||||
currentUserID := ctxUserID(ctx)
|
||||
currentUserID := common.GetCtxUserID(ctx)
|
||||
|
||||
if req.Id == currentUserID {
|
||||
return nil, common.NewBadRequestError("不能修改当前登录用户的状态")
|
||||
@@ -309,67 +308,23 @@ func buildUserFilters(m *gdb.Model, keyword, role, status string) *gdb.Model {
|
||||
|
||||
// ExportUsers exports admin users to CSV or Excel.
|
||||
func (s *sAdmin) ExportUsers(ctx context.Context, req *v1.AdminUserExportReq) (*v1.AdminUserExportRes, error) {
|
||||
r := g.RequestFromCtx(ctx)
|
||||
format := req.Format
|
||||
if format == "" {
|
||||
format = "csv"
|
||||
}
|
||||
if format == "excel" {
|
||||
format = "xlsx"
|
||||
}
|
||||
|
||||
columns := []export.Column{
|
||||
{Field: "id", Header: "ID"},
|
||||
{Field: "username", Header: "用户名"},
|
||||
{Field: "email", Header: "邮箱"},
|
||||
{Field: "display_name", Header: "显示名称"},
|
||||
{Field: "role", Header: "角色"},
|
||||
{Field: "status", Header: "状态"},
|
||||
{Field: "last_login_at", Header: "最后登录时间"},
|
||||
{Field: "last_login_ip", Header: "最后登录IP"},
|
||||
{Field: "created_at", Header: "创建时间"},
|
||||
}
|
||||
|
||||
config := export.Config{
|
||||
Format: format,
|
||||
Format: req.Format,
|
||||
Filename: "管理员_" + gtime.Now().Format("Ymd_His"),
|
||||
Columns: columns,
|
||||
Columns: []export.Column{
|
||||
{Field: "id", Header: "ID"},
|
||||
{Field: "username", Header: "用户名"},
|
||||
{Field: "email", Header: "邮箱"},
|
||||
{Field: "display_name", Header: "显示名称"},
|
||||
{Field: "role", Header: "角色"},
|
||||
{Field: "status", Header: "状态"},
|
||||
{Field: "last_login_at", Header: "最后登录时间"},
|
||||
{Field: "last_login_ip", Header: "最后登录IP"},
|
||||
{Field: "created_at", Header: "创建时间"},
|
||||
},
|
||||
}
|
||||
|
||||
if format == "xlsx" {
|
||||
m := buildUserFilters(dao.SysAdminUsers.Ctx(ctx), req.Keyword, req.Role, req.Status)
|
||||
var users []struct {
|
||||
Id int64 `json:"id"`
|
||||
Username string `json:"username"`
|
||||
Email string `json:"email"`
|
||||
DisplayName string `json:"display_name"`
|
||||
Role string `json:"role"`
|
||||
Status string `json:"status"`
|
||||
LastLoginAt *gtime.Time `json:"last_login_at"`
|
||||
LastLoginIp string `json:"last_login_ip"`
|
||||
CreatedAt *gtime.Time `json:"created_at"`
|
||||
}
|
||||
if err := m.OrderDesc("id").Scan(&users); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
data := make([]map[string]any, len(users))
|
||||
for i, u := range users {
|
||||
data[i] = map[string]any{
|
||||
"id": u.Id,
|
||||
"username": u.Username,
|
||||
"email": u.Email,
|
||||
"display_name": u.DisplayName,
|
||||
"role": u.Role,
|
||||
"status": u.Status,
|
||||
"last_login_at": u.LastLoginAt.String(),
|
||||
"last_login_ip": u.LastLoginIp,
|
||||
"created_at": u.CreatedAt.String(),
|
||||
}
|
||||
}
|
||||
return nil, export.WriteExcel(r, config, data)
|
||||
}
|
||||
|
||||
return nil, export.StreamCSV(r, config, func(yield func(map[string]any) bool) {
|
||||
return nil, export.GenericExport(ctx, config, func(yield func(map[string]any) bool) {
|
||||
offset := 0
|
||||
for {
|
||||
m := buildUserFilters(dao.SysAdminUsers.Ctx(ctx), req.Keyword, req.Role, req.Status)
|
||||
|
||||
@@ -340,33 +340,6 @@ func (s *sAdmin) GetRequestAuditLogDetail(ctx context.Context, req *v1.RequestAu
|
||||
|
||||
// ExportOperationLogs exports operation logs to CSV or Excel.
|
||||
func (s *sAdmin) ExportOperationLogs(ctx context.Context, req *v1.OperationLogExportReq) (*v1.OperationLogExportRes, error) {
|
||||
r := g.RequestFromCtx(ctx)
|
||||
format := req.Format
|
||||
if format == "" {
|
||||
format = "csv"
|
||||
}
|
||||
if format == "excel" {
|
||||
format = "xlsx"
|
||||
}
|
||||
|
||||
columns := []export.Column{
|
||||
{Field: "id", Header: "ID"},
|
||||
{Field: "user_id", Header: "用户ID"},
|
||||
{Field: "user_type", Header: "用户类型"},
|
||||
{Field: "action", Header: "操作"},
|
||||
{Field: "resource_type", Header: "目标类型"},
|
||||
{Field: "resource_id", Header: "目标ID"},
|
||||
{Field: "ip_address", Header: "IP地址"},
|
||||
{Field: "detail", Header: "详情"},
|
||||
{Field: "created_at", Header: "创建时间"},
|
||||
}
|
||||
|
||||
config := export.Config{
|
||||
Format: format,
|
||||
Filename: "操作日志_" + gtime.Now().Format("Ymd_His"),
|
||||
Columns: columns,
|
||||
}
|
||||
|
||||
buildOpLogWhere := func() (string, []any) {
|
||||
var conditions []string
|
||||
var args []any
|
||||
@@ -399,35 +372,23 @@ func (s *sAdmin) ExportOperationLogs(ctx context.Context, req *v1.OperationLogEx
|
||||
|
||||
selectFields := "id, user_id, user_type, action, resource_type, resource_id, ip_address, detail, created_at"
|
||||
|
||||
if format == "xlsx" {
|
||||
where, args := buildOpLogWhere()
|
||||
items, _, err := queryPage(ctx, "aud_operation_logs", selectFields, where, "created_at DESC", 1, 100000, args...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Ensure time fields are formatted as strings
|
||||
data := make([]map[string]any, len(items))
|
||||
for i, item := range items {
|
||||
createdAt := ""
|
||||
if v, ok := item["created_at"]; ok {
|
||||
createdAt = fmt.Sprintf("%v", v)
|
||||
}
|
||||
data[i] = map[string]any{
|
||||
"id": item["id"],
|
||||
"user_id": item["user_id"],
|
||||
"user_type": item["user_type"],
|
||||
"action": item["action"],
|
||||
"resource_type": item["resource_type"],
|
||||
"resource_id": item["resource_id"],
|
||||
"ip_address": item["ip_address"],
|
||||
"detail": item["detail"],
|
||||
"created_at": createdAt,
|
||||
}
|
||||
}
|
||||
return nil, export.WriteExcel(r, config, data)
|
||||
config := export.Config{
|
||||
Format: req.Format,
|
||||
Filename: "操作日志_" + gtime.Now().Format("Ymd_His"),
|
||||
Columns: []export.Column{
|
||||
{Field: "id", Header: "ID"},
|
||||
{Field: "user_id", Header: "用户ID"},
|
||||
{Field: "user_type", Header: "用户类型"},
|
||||
{Field: "action", Header: "操作"},
|
||||
{Field: "resource_type", Header: "目标类型"},
|
||||
{Field: "resource_id", Header: "目标ID"},
|
||||
{Field: "ip_address", Header: "IP地址"},
|
||||
{Field: "detail", Header: "详情"},
|
||||
{Field: "created_at", Header: "创建时间"},
|
||||
},
|
||||
}
|
||||
|
||||
return nil, export.StreamCSV(r, config, func(yield func(map[string]any) bool) {
|
||||
return nil, export.GenericExport(ctx, config, func(yield func(map[string]any) bool) {
|
||||
offset := 0
|
||||
for {
|
||||
where, args := buildOpLogWhere()
|
||||
|
||||
@@ -19,29 +19,8 @@ import (
|
||||
"github.com/qianfree/team-api/internal/utility/crypto"
|
||||
)
|
||||
|
||||
// ctxUserID extracts user ID from context.
|
||||
func ctxUserID(ctx context.Context) int64 {
|
||||
val := ctx.Value("userId")
|
||||
if val == nil {
|
||||
return 0
|
||||
}
|
||||
if id, ok := val.(int64); ok {
|
||||
return id
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// ctxSessionID extracts session ID from context.
|
||||
func ctxSessionID(ctx context.Context) int64 {
|
||||
val := ctx.Value("sessionId")
|
||||
if val == nil {
|
||||
return 0
|
||||
}
|
||||
if id, ok := val.(int64); ok {
|
||||
return id
|
||||
}
|
||||
return 0
|
||||
}
|
||||
// NOTE: ctxUserID and ctxSessionID have been removed.
|
||||
// Use common.GetCtxUserID(ctx) and common.GetCtxSessionID(ctx) instead.
|
||||
|
||||
// Login handles admin login.
|
||||
func (s *sAdmin) Login(ctx context.Context, req *v1.AdminLoginReq) (*v1.AdminLoginRes, error) {
|
||||
@@ -159,7 +138,7 @@ func (s *sAdmin) Login(ctx context.Context, req *v1.AdminLoginReq) (*v1.AdminLog
|
||||
|
||||
// Logout handles admin logout.
|
||||
func (s *sAdmin) Logout(ctx context.Context, _ *v1.AdminLogoutReq) (*v1.AdminLogoutRes, error) {
|
||||
sessionID := ctxSessionID(ctx)
|
||||
sessionID := common.GetCtxSessionID(ctx)
|
||||
|
||||
// Mark session as revoked in Redis for instant effect
|
||||
common.MarkSessionRevoked(ctx, sessionID)
|
||||
@@ -235,7 +214,7 @@ func (s *sAdmin) Refresh(ctx context.Context, req *v1.AdminRefreshReq) (*v1.Admi
|
||||
|
||||
// ListSessions returns active sessions for the current admin user.
|
||||
func (s *sAdmin) ListSessions(ctx context.Context, req *v1.AdminSessionListReq) (*v1.AdminSessionListRes, error) {
|
||||
currentSessionID := ctxSessionID(ctx)
|
||||
currentSessionID := common.GetCtxSessionID(ctx)
|
||||
page := req.Page
|
||||
pageSize := req.PageSize
|
||||
if page < 1 {
|
||||
@@ -346,7 +325,7 @@ func (s *sAdmin) ForceLogout(ctx context.Context, req *v1.AdminForceLogoutReq) (
|
||||
|
||||
// ChangePassword handles admin password change.
|
||||
func (s *sAdmin) ChangePassword(ctx context.Context, req *v1.AdminChangePasswordReq) (*v1.AdminChangePasswordRes, error) {
|
||||
userID := ctxUserID(ctx)
|
||||
userID := common.GetCtxUserID(ctx)
|
||||
|
||||
var user *entity.SysAdminUsers
|
||||
err := dao.SysAdminUsers.Ctx(ctx).
|
||||
|
||||
@@ -18,7 +18,7 @@ func (s *sAdmin) CreateChangelog(ctx context.Context, req *v1.ChangelogCreateReq
|
||||
"content": req.Content,
|
||||
"type": req.Type,
|
||||
"status": "draft",
|
||||
"created_by": getCtxUserID(ctx),
|
||||
"created_by": common.GetCtxUserID(ctx),
|
||||
}).Insert()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -618,84 +618,24 @@ func (s *sAdmin) GetChannelHealthTrend(ctx context.Context, req *v1.ChannelHealt
|
||||
|
||||
// ExportChannels exports channel list to CSV or Excel.
|
||||
func (s *sAdmin) ExportChannels(ctx context.Context, req *v1.ChannelExportReq) (*v1.ChannelExportRes, error) {
|
||||
r := g.RequestFromCtx(ctx)
|
||||
format := req.Format
|
||||
if format == "" {
|
||||
format = "csv"
|
||||
}
|
||||
if format == "excel" {
|
||||
format = "xlsx"
|
||||
}
|
||||
|
||||
columns := []export.Column{
|
||||
{Field: "id", Header: "ID"},
|
||||
{Field: "name", Header: "名称"},
|
||||
{Field: "type_name", Header: "供应商类型"},
|
||||
{Field: "status", Header: "状态"},
|
||||
{Field: "priority", Header: "优先级"},
|
||||
{Field: "weight", Header: "权重"},
|
||||
{Field: "health_score", Header: "健康分数"},
|
||||
{Field: "created_at", Header: "创建时间"},
|
||||
}
|
||||
|
||||
config := export.Config{
|
||||
Format: format,
|
||||
Filename: "渠道_" + gtime.Now().Format("Ymd_His"),
|
||||
Columns: columns,
|
||||
}
|
||||
|
||||
channelFields := "chn_channels.id, chn_channels.name, chn_channels.type, chn_channels.status, chn_channels.priority, chn_channels.weight, chn_channels.created_at, h.health_score"
|
||||
|
||||
if format == "xlsx" {
|
||||
query := dao.ChnChannels.Ctx(ctx).
|
||||
LeftJoin("chn_health_scores h ON chn_channels.id = h.channel_id")
|
||||
if req.Type > 0 {
|
||||
query = query.Where("chn_channels.type", req.Type)
|
||||
}
|
||||
if req.Status != "" {
|
||||
query = query.Where("chn_channels.status", req.Status)
|
||||
}
|
||||
if req.Search != "" {
|
||||
query = query.Where("chn_channels.name LIKE ? OR chn_channels.remark LIKE ?", "%"+req.Search+"%", "%"+req.Search+"%")
|
||||
}
|
||||
var channels []struct {
|
||||
ID int64 `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Type int `json:"type"`
|
||||
Status string `json:"status"`
|
||||
Priority int `json:"priority"`
|
||||
Weight int `json:"weight"`
|
||||
CreatedAt *gtime.Time `json:"created_at"`
|
||||
HealthScore *float64 `json:"health_score"`
|
||||
}
|
||||
if err := query.Fields(channelFields).OrderDesc("chn_channels.priority").Scan(&channels); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
data := make([]map[string]any, len(channels))
|
||||
for i, ch := range channels {
|
||||
typeName := providerTypeNames[ch.Type]
|
||||
if typeName == "" {
|
||||
typeName = fmt.Sprintf("Unknown(%d)", ch.Type)
|
||||
}
|
||||
healthScore := ""
|
||||
if ch.HealthScore != nil {
|
||||
healthScore = fmt.Sprintf("%.2f", *ch.HealthScore)
|
||||
}
|
||||
data[i] = map[string]any{
|
||||
"id": ch.ID,
|
||||
"name": ch.Name,
|
||||
"type_name": typeName,
|
||||
"status": ch.Status,
|
||||
"priority": ch.Priority,
|
||||
"weight": ch.Weight,
|
||||
"health_score": healthScore,
|
||||
"created_at": ch.CreatedAt.String(),
|
||||
}
|
||||
}
|
||||
return nil, export.WriteExcel(r, config, data)
|
||||
config := export.Config{
|
||||
Format: req.Format,
|
||||
Filename: "渠道_" + gtime.Now().Format("Ymd_His"),
|
||||
Columns: []export.Column{
|
||||
{Field: "id", Header: "ID"},
|
||||
{Field: "name", Header: "名称"},
|
||||
{Field: "type_name", Header: "供应商类型"},
|
||||
{Field: "status", Header: "状态"},
|
||||
{Field: "priority", Header: "优先级"},
|
||||
{Field: "weight", Header: "权重"},
|
||||
{Field: "health_score", Header: "健康分数"},
|
||||
{Field: "created_at", Header: "创建时间"},
|
||||
},
|
||||
}
|
||||
|
||||
return nil, export.StreamCSV(r, config, func(yield func(map[string]any) bool) {
|
||||
return nil, export.GenericExport(ctx, config, func(yield func(map[string]any) bool) {
|
||||
offset := 0
|
||||
for {
|
||||
query := dao.ChnChannels.Ctx(ctx).
|
||||
|
||||
@@ -549,15 +549,6 @@ func (s *sAdmin) GetDashboardRecentAlerts(ctx context.Context, req *v1.AdminDash
|
||||
|
||||
// ExportUsageLogs exports usage logs to CSV or Excel.
|
||||
func (s *sAdmin) ExportUsageLogs(ctx context.Context, req *v1.AdminUsageLogExportReq) (*v1.AdminUsageLogExportRes, error) {
|
||||
r := g.RequestFromCtx(ctx)
|
||||
format := req.Format
|
||||
if format == "" {
|
||||
format = "csv"
|
||||
}
|
||||
if format == "excel" {
|
||||
format = "xlsx"
|
||||
}
|
||||
|
||||
columns := []export.Column{
|
||||
{Field: "id", Header: "ID"},
|
||||
{Field: "tenant_name", Header: "租户名称"},
|
||||
@@ -572,7 +563,7 @@ func (s *sAdmin) ExportUsageLogs(ctx context.Context, req *v1.AdminUsageLogExpor
|
||||
}
|
||||
|
||||
config := export.Config{
|
||||
Format: format,
|
||||
Format: req.Format,
|
||||
Filename: "用量日志_" + gtime.Now().Format("Ymd_His"),
|
||||
Columns: columns,
|
||||
}
|
||||
@@ -618,36 +609,7 @@ func (s *sAdmin) ExportUsageLogs(ctx context.Context, req *v1.AdminUsageLogExpor
|
||||
fromClause := "bil_usage_logs u LEFT JOIN tnt_users t ON u.user_id = t.id AND u.tenant_id = t.tenant_id LEFT JOIN tnt_tenants tn ON u.tenant_id = tn.id"
|
||||
selectFields := "u.id, COALESCE(tn.name, '') AS tenant_name, COALESCE(t.username, '') AS username, u.model_name, u.request_type, u.input_tokens, u.output_tokens, u.total_cost, u.status, u.created_at"
|
||||
|
||||
if format == "xlsx" {
|
||||
where, args := buildUsageWhere()
|
||||
sql := fmt.Sprintf("SELECT %s FROM %s%s ORDER BY u.created_at DESC", selectFields, fromClause, where)
|
||||
result, err := g.DB().Ctx(ctx).Query(ctx, sql, args...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
data := make([]map[string]any, len(result))
|
||||
for i, row := range result {
|
||||
createdAt := ""
|
||||
if t, ok := row["created_at"]; ok {
|
||||
createdAt = fmt.Sprintf("%v", t.Val())
|
||||
}
|
||||
data[i] = map[string]any{
|
||||
"id": row["id"].Val(),
|
||||
"tenant_name": row["tenant_name"].Val(),
|
||||
"username": row["username"].Val(),
|
||||
"model_name": row["model_name"].Val(),
|
||||
"request_type": row["request_type"].Val(),
|
||||
"input_tokens": row["input_tokens"].Val(),
|
||||
"output_tokens": row["output_tokens"].Val(),
|
||||
"total_cost": row["total_cost"].Val(),
|
||||
"status": row["status"].Val(),
|
||||
"created_at": createdAt,
|
||||
}
|
||||
}
|
||||
return nil, export.WriteExcel(r, config, data)
|
||||
}
|
||||
|
||||
return nil, export.StreamCSV(r, config, func(yield func(map[string]any) bool) {
|
||||
return nil, export.GenericExport(ctx, config, func(yield func(map[string]any) bool) {
|
||||
offset := 0
|
||||
for {
|
||||
where, args := buildUsageWhere()
|
||||
@@ -686,15 +648,6 @@ func (s *sAdmin) ExportUsageLogs(ctx context.Context, req *v1.AdminUsageLogExpor
|
||||
|
||||
// ExportBillingRecords exports billing records to CSV or Excel.
|
||||
func (s *sAdmin) ExportBillingRecords(ctx context.Context, req *v1.AdminBillingRecordExportReq) (*v1.AdminBillingRecordExportRes, error) {
|
||||
r := g.RequestFromCtx(ctx)
|
||||
format := req.Format
|
||||
if format == "" {
|
||||
format = "csv"
|
||||
}
|
||||
if format == "excel" {
|
||||
format = "xlsx"
|
||||
}
|
||||
|
||||
columns := []export.Column{
|
||||
{Field: "id", Header: "ID"},
|
||||
{Field: "tenant_name", Header: "租户名称"},
|
||||
@@ -709,7 +662,7 @@ func (s *sAdmin) ExportBillingRecords(ctx context.Context, req *v1.AdminBillingR
|
||||
}
|
||||
|
||||
config := export.Config{
|
||||
Format: format,
|
||||
Format: req.Format,
|
||||
Filename: "计费记录_" + gtime.Now().Format("Ymd_His"),
|
||||
Columns: columns,
|
||||
}
|
||||
@@ -801,15 +754,7 @@ func (s *sAdmin) ExportBillingRecords(ctx context.Context, req *v1.AdminBillingR
|
||||
return data, nil
|
||||
}
|
||||
|
||||
if format == "xlsx" {
|
||||
data, err := fetchRecords(0, 100000)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return nil, export.WriteExcel(r, config, data)
|
||||
}
|
||||
|
||||
return nil, export.StreamCSV(r, config, func(yield func(map[string]any) bool) {
|
||||
return nil, export.GenericExport(ctx, config, func(yield func(map[string]any) bool) {
|
||||
offset := 0
|
||||
for {
|
||||
batch, err := fetchRecords(offset, 1000)
|
||||
|
||||
@@ -59,7 +59,7 @@ func (s *sAdmin) DataGovernanceExport(ctx context.Context, req *v1.DataGovernanc
|
||||
payload, _ := json.Marshal(map[string]any{
|
||||
"tenant_id": req.TenantID,
|
||||
"scopes": req.Scopes,
|
||||
"requested_by": getCtxUserID(ctx),
|
||||
"requested_by": common.GetCtxUserID(ctx),
|
||||
})
|
||||
result, err := dao.TskTasks.Ctx(ctx).Data(do.TskTasks{
|
||||
Name: fmt.Sprintf("数据导出 [租户%d]", req.TenantID),
|
||||
@@ -93,7 +93,7 @@ func (s *sAdmin) DataGovernanceDeletion(ctx context.Context, req *v1.DataGoverna
|
||||
payload, _ := json.Marshal(map[string]any{
|
||||
"tenant_id": req.TenantID,
|
||||
"reason": req.Reason,
|
||||
"requested_by": getCtxUserID(ctx),
|
||||
"requested_by": common.GetCtxUserID(ctx),
|
||||
})
|
||||
result, err := dao.TskTasks.Ctx(ctx).Data(do.TskTasks{
|
||||
Name: fmt.Sprintf("数据删除 [租户%d]", req.TenantID),
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
|
||||
v1 "github.com/qianfree/team-api/api/admin/v1"
|
||||
"github.com/qianfree/team-api/internal/dao"
|
||||
"github.com/qianfree/team-api/internal/logic/common"
|
||||
)
|
||||
|
||||
// ErrorLogList returns a paginated list of system error logs.
|
||||
@@ -83,7 +84,7 @@ func (s *sAdmin) ErrorLogDetail(ctx context.Context, req *v1.ErrorLogDetailReq)
|
||||
|
||||
// ErrorLogResolve marks an error log as resolved.
|
||||
func (s *sAdmin) ErrorLogResolve(ctx context.Context, req *v1.ErrorLogResolveReq) (*v1.ErrorLogResolveRes, error) {
|
||||
userID := getCtxUserID(ctx)
|
||||
userID := common.GetCtxUserID(ctx)
|
||||
_, err := dao.SysErrorLogs.Ctx(ctx).
|
||||
Where("id", req.Id).
|
||||
Data(g.Map{
|
||||
@@ -99,7 +100,7 @@ func (s *sAdmin) ErrorLogResolve(ctx context.Context, req *v1.ErrorLogResolveReq
|
||||
|
||||
// ErrorLogBatchResolve marks multiple error logs as resolved.
|
||||
func (s *sAdmin) ErrorLogBatchResolve(ctx context.Context, req *v1.ErrorLogBatchResolveReq) (*v1.ErrorLogBatchResolveRes, error) {
|
||||
userID := getCtxUserID(ctx)
|
||||
userID := common.GetCtxUserID(ctx)
|
||||
_, err := dao.SysErrorLogs.Ctx(ctx).
|
||||
WhereIn("id", req.Ids).
|
||||
Data(g.Map{
|
||||
|
||||
@@ -104,7 +104,7 @@ func (s *sAdmin) ReplyToFeedback(ctx context.Context, req *v1.FeedbackReplyReq)
|
||||
|
||||
updateData := g.Map{
|
||||
"admin_reply": req.Reply,
|
||||
"admin_reply_by": getCtxUserID(ctx),
|
||||
"admin_reply_by": common.GetCtxUserID(ctx),
|
||||
"admin_reply_at": gtime.Now(),
|
||||
}
|
||||
if req.Status != "" {
|
||||
|
||||
@@ -197,7 +197,7 @@ func (s *sAdmin) CreateHelpArticle(ctx context.Context, req *v1.HelpArticleCreat
|
||||
"content": req.Content,
|
||||
"summary": req.Summary,
|
||||
"status": req.Status,
|
||||
"author_id": getCtxUserID(ctx),
|
||||
"author_id": common.GetCtxUserID(ctx),
|
||||
"sort_order": req.SortOrder,
|
||||
"keywords": req.Keywords,
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@ import (
|
||||
|
||||
"github.com/gogf/gf/v2/database/gdb"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/os/gtime"
|
||||
|
||||
v1 "github.com/qianfree/team-api/api/admin/v1"
|
||||
@@ -351,76 +350,25 @@ func buildMemberFilters(m *gdb.Model, keyword, status, role string, tenantID int
|
||||
|
||||
// ExportMembers exports member list to CSV or Excel.
|
||||
func (s *sAdmin) ExportMembers(ctx context.Context, req *v1.AdminMemberExportReq) (*v1.AdminMemberExportRes, error) {
|
||||
r := g.RequestFromCtx(ctx)
|
||||
format := req.Format
|
||||
if format == "" {
|
||||
format = "csv"
|
||||
}
|
||||
if format == "excel" {
|
||||
format = "xlsx"
|
||||
}
|
||||
|
||||
columns := []export.Column{
|
||||
{Field: "id", Header: "ID"},
|
||||
{Field: "tenant_name", Header: "租户名称"},
|
||||
{Field: "username", Header: "用户名"},
|
||||
{Field: "email", Header: "邮箱"},
|
||||
{Field: "display_name", Header: "显示名称"},
|
||||
{Field: "role", Header: "角色"},
|
||||
{Field: "status", Header: "状态"},
|
||||
{Field: "last_login_at", Header: "最后登录时间"},
|
||||
{Field: "created_at", Header: "创建时间"},
|
||||
}
|
||||
|
||||
config := export.Config{
|
||||
Format: format,
|
||||
Filename: "成员_" + gtime.Now().Format("Ymd_His"),
|
||||
Columns: columns,
|
||||
}
|
||||
|
||||
memberFields := "tnt_users.id, tnt_users.tenant_id, t.name as tenant_name, t.code as tenant_code, tnt_users.username, tnt_users.email, tnt_users.display_name, tnt_users.role, tnt_users.status, tnt_users.last_login_at, tnt_users.last_login_ip, tnt_users.failed_attempts, tnt_users.created_at"
|
||||
|
||||
if format == "xlsx" {
|
||||
m := buildMemberFilters(
|
||||
dao.TntUsers.Ctx(ctx).LeftJoin("tnt_tenants t ON tnt_users.tenant_id = t.id"),
|
||||
req.Keyword, req.Status, req.Role, req.TenantID,
|
||||
)
|
||||
var members []struct {
|
||||
Id int64 `json:"id"`
|
||||
TenantId int64 `json:"tenant_id"`
|
||||
TenantName string `json:"tenant_name"`
|
||||
TenantCode string `json:"tenant_code"`
|
||||
Username string `json:"username"`
|
||||
Email string `json:"email"`
|
||||
DisplayName string `json:"display_name"`
|
||||
Role string `json:"role"`
|
||||
Status string `json:"status"`
|
||||
LastLoginAt *gtime.Time `json:"last_login_at"`
|
||||
LastLoginIp string `json:"last_login_ip"`
|
||||
FailedAttempts int `json:"failed_attempts"`
|
||||
CreatedAt *gtime.Time `json:"created_at"`
|
||||
}
|
||||
if err := m.Fields(memberFields).OrderDesc("tnt_users.id").Scan(&members); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
data := make([]map[string]any, len(members))
|
||||
for i, m := range members {
|
||||
data[i] = map[string]any{
|
||||
"id": m.Id,
|
||||
"tenant_name": m.TenantName,
|
||||
"username": m.Username,
|
||||
"email": m.Email,
|
||||
"display_name": m.DisplayName,
|
||||
"role": m.Role,
|
||||
"status": m.Status,
|
||||
"last_login_at": m.LastLoginAt.String(),
|
||||
"created_at": m.CreatedAt.String(),
|
||||
}
|
||||
}
|
||||
return nil, export.WriteExcel(r, config, data)
|
||||
config := export.Config{
|
||||
Format: req.Format,
|
||||
Filename: "成员_" + gtime.Now().Format("Ymd_His"),
|
||||
Columns: []export.Column{
|
||||
{Field: "id", Header: "ID"},
|
||||
{Field: "tenant_name", Header: "租户名称"},
|
||||
{Field: "username", Header: "用户名"},
|
||||
{Field: "email", Header: "邮箱"},
|
||||
{Field: "display_name", Header: "显示名称"},
|
||||
{Field: "role", Header: "角色"},
|
||||
{Field: "status", Header: "状态"},
|
||||
{Field: "last_login_at", Header: "最后登录时间"},
|
||||
{Field: "created_at", Header: "创建时间"},
|
||||
},
|
||||
}
|
||||
|
||||
return nil, export.StreamCSV(r, config, func(yield func(map[string]any) bool) {
|
||||
return nil, export.GenericExport(ctx, config, func(yield func(map[string]any) bool) {
|
||||
offset := 0
|
||||
for {
|
||||
m := buildMemberFilters(
|
||||
@@ -428,19 +376,15 @@ func (s *sAdmin) ExportMembers(ctx context.Context, req *v1.AdminMemberExportReq
|
||||
req.Keyword, req.Status, req.Role, req.TenantID,
|
||||
)
|
||||
var batch []struct {
|
||||
Id int64 `json:"id"`
|
||||
TenantId int64 `json:"tenant_id"`
|
||||
TenantName string `json:"tenant_name"`
|
||||
TenantCode string `json:"tenant_code"`
|
||||
Username string `json:"username"`
|
||||
Email string `json:"email"`
|
||||
DisplayName string `json:"display_name"`
|
||||
Role string `json:"role"`
|
||||
Status string `json:"status"`
|
||||
LastLoginAt *gtime.Time `json:"last_login_at"`
|
||||
LastLoginIp string `json:"last_login_ip"`
|
||||
FailedAttempts int `json:"failed_attempts"`
|
||||
CreatedAt *gtime.Time `json:"created_at"`
|
||||
Id int64 `json:"id"`
|
||||
TenantName string `json:"tenant_name"`
|
||||
Username string `json:"username"`
|
||||
Email string `json:"email"`
|
||||
DisplayName string `json:"display_name"`
|
||||
Role string `json:"role"`
|
||||
Status string `json:"status"`
|
||||
LastLoginAt *gtime.Time `json:"last_login_at"`
|
||||
CreatedAt *gtime.Time `json:"created_at"`
|
||||
}
|
||||
if err := m.Fields(memberFields).OrderDesc("tnt_users.id").Limit(1000).Offset(offset).Scan(&batch); err != nil {
|
||||
return
|
||||
|
||||
@@ -627,75 +627,24 @@ func (s *sAdmin) FetchOfficialModelInfo(ctx context.Context, req *v1.ModelFetchO
|
||||
|
||||
// ExportModels exports model list to CSV or Excel.
|
||||
func (s *sAdmin) ExportModels(ctx context.Context, req *v1.ModelExportReq) (*v1.ModelExportRes, error) {
|
||||
r := g.RequestFromCtx(ctx)
|
||||
format := req.Format
|
||||
if format == "" {
|
||||
format = "csv"
|
||||
}
|
||||
if format == "excel" {
|
||||
format = "xlsx"
|
||||
}
|
||||
|
||||
columns := []export.Column{
|
||||
{Field: "id", Header: "ID"},
|
||||
{Field: "model_id", Header: "模型标识"},
|
||||
{Field: "model_name", Header: "显示名称"},
|
||||
{Field: "category", Header: "分类"},
|
||||
{Field: "status", Header: "状态"},
|
||||
{Field: "max_context_tokens", Header: "最大上下文"},
|
||||
{Field: "max_output_tokens", Header: "最大输出"},
|
||||
{Field: "created_at", Header: "创建时间"},
|
||||
}
|
||||
|
||||
config := export.Config{
|
||||
Format: format,
|
||||
Filename: "模型_" + gtime.Now().Format("Ymd_His"),
|
||||
Columns: columns,
|
||||
}
|
||||
|
||||
modelFields := "id, model_id, model_name, category, status, max_context_tokens, max_output_tokens, created_at"
|
||||
|
||||
if format == "xlsx" {
|
||||
query := dao.MdlModels.Ctx(ctx)
|
||||
if req.Category != "" {
|
||||
query = query.Where("category", req.Category)
|
||||
}
|
||||
if req.Status != "" {
|
||||
query = query.Where("status", req.Status)
|
||||
}
|
||||
if req.Search != "" {
|
||||
query = query.Where("model_id LIKE ? OR model_name LIKE ?", "%"+req.Search+"%", "%"+req.Search+"%")
|
||||
}
|
||||
var models []struct {
|
||||
ID int64 `json:"id"`
|
||||
ModelId string `json:"model_id"`
|
||||
ModelName string `json:"model_name"`
|
||||
Category string `json:"category"`
|
||||
Status string `json:"status"`
|
||||
MaxContextTokens int `json:"max_context_tokens"`
|
||||
MaxOutputTokens int `json:"max_output_tokens"`
|
||||
CreatedAt *gtime.Time `json:"created_at"`
|
||||
}
|
||||
if err := query.Fields(modelFields).OrderDesc("id").Scan(&models); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
data := make([]map[string]any, len(models))
|
||||
for i, m := range models {
|
||||
data[i] = map[string]any{
|
||||
"id": m.ID,
|
||||
"model_id": m.ModelId,
|
||||
"model_name": m.ModelName,
|
||||
"category": m.Category,
|
||||
"status": m.Status,
|
||||
"max_context_tokens": m.MaxContextTokens,
|
||||
"max_output_tokens": m.MaxOutputTokens,
|
||||
"created_at": m.CreatedAt.String(),
|
||||
}
|
||||
}
|
||||
return nil, export.WriteExcel(r, config, data)
|
||||
config := export.Config{
|
||||
Format: req.Format,
|
||||
Filename: "模型_" + gtime.Now().Format("Ymd_His"),
|
||||
Columns: []export.Column{
|
||||
{Field: "id", Header: "ID"},
|
||||
{Field: "model_id", Header: "模型标识"},
|
||||
{Field: "model_name", Header: "显示名称"},
|
||||
{Field: "category", Header: "分类"},
|
||||
{Field: "status", Header: "状态"},
|
||||
{Field: "max_context_tokens", Header: "最大上下文"},
|
||||
{Field: "max_output_tokens", Header: "最大输出"},
|
||||
{Field: "created_at", Header: "创建时间"},
|
||||
},
|
||||
}
|
||||
|
||||
return nil, export.StreamCSV(r, config, func(yield func(map[string]any) bool) {
|
||||
return nil, export.GenericExport(ctx, config, func(yield func(map[string]any) bool) {
|
||||
offset := 0
|
||||
for {
|
||||
query := dao.MdlModels.Ctx(ctx)
|
||||
|
||||
@@ -255,7 +255,7 @@ func (s *sAdmin) CreateAnnouncement(ctx context.Context, req *v1.AnnouncementCre
|
||||
}
|
||||
|
||||
// Get admin user ID from context
|
||||
adminUserID := getCtxUserID(ctx)
|
||||
adminUserID := common.GetCtxUserID(ctx)
|
||||
|
||||
data := do.NtfAnnouncements{
|
||||
Title: req.Title,
|
||||
|
||||
@@ -4,7 +4,6 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/os/gtime"
|
||||
v1 "github.com/qianfree/team-api/api/admin/v1"
|
||||
"github.com/qianfree/team-api/internal/dao"
|
||||
@@ -61,7 +60,7 @@ func (s *sAdmin) GetOrder(ctx context.Context, req *v1.OrderDetailReq) (*v1.Orde
|
||||
|
||||
// RefundOrder 发起退款
|
||||
func (s *sAdmin) RefundOrder(ctx context.Context, req *v1.OrderRefundReq) (*v1.OrderRefundRes, error) {
|
||||
adminUserID := ctxUserID(ctx)
|
||||
adminUserID := common.GetCtxUserID(ctx)
|
||||
|
||||
var order struct {
|
||||
TenantID int64 `json:"tenant_id"`
|
||||
@@ -107,7 +106,7 @@ func (s *sAdmin) RefundOrder(ctx context.Context, req *v1.OrderRefundReq) (*v1.O
|
||||
|
||||
// OrderComplete 手动完成订单
|
||||
func (s *sAdmin) OrderComplete(ctx context.Context, req *v1.OrderCompleteReq) (*v1.OrderCompleteRes, error) {
|
||||
adminUserID := ctxUserID(ctx)
|
||||
adminUserID := common.GetCtxUserID(ctx)
|
||||
|
||||
orderNo, err := getOrderForComplete(ctx, req.Id)
|
||||
if err != nil {
|
||||
@@ -208,15 +207,6 @@ func markOrderPaidByAdmin(ctx context.Context, orderID int64, adminUserID int64)
|
||||
|
||||
// ExportOrders exports order list to CSV or Excel.
|
||||
func (s *sAdmin) ExportOrders(ctx context.Context, req *v1.OrderExportReq) (*v1.OrderExportRes, error) {
|
||||
r := g.RequestFromCtx(ctx)
|
||||
format := req.Format
|
||||
if format == "" {
|
||||
format = "csv"
|
||||
}
|
||||
if format == "excel" {
|
||||
format = "xlsx"
|
||||
}
|
||||
|
||||
columns := []export.Column{
|
||||
{Field: "id", Header: "ID"},
|
||||
{Field: "order_no", Header: "订单号"},
|
||||
@@ -229,51 +219,14 @@ func (s *sAdmin) ExportOrders(ctx context.Context, req *v1.OrderExportReq) (*v1.
|
||||
}
|
||||
|
||||
config := export.Config{
|
||||
Format: format,
|
||||
Format: req.Format,
|
||||
Filename: "订单_" + gtime.Now().Format("Ymd_His"),
|
||||
Columns: columns,
|
||||
}
|
||||
|
||||
orderFields := "id, order_no, tenant_id, order_type, final_amount, payment_channel, status, created_at"
|
||||
|
||||
if format == "xlsx" {
|
||||
query := dao.OrdOrders.Ctx(ctx)
|
||||
if req.Status != "" {
|
||||
query = query.Where("status", req.Status)
|
||||
}
|
||||
if req.TenantID != "" {
|
||||
query = query.Where("tenant_id", req.TenantID)
|
||||
}
|
||||
var orders []struct {
|
||||
Id int64 `json:"id"`
|
||||
OrderNo string `json:"order_no"`
|
||||
TenantId int64 `json:"tenant_id"`
|
||||
OrderType string `json:"order_type"`
|
||||
FinalAmount float64 `json:"final_amount"`
|
||||
PaymentChannel string `json:"payment_channel"`
|
||||
Status string `json:"status"`
|
||||
CreatedAt *gtime.Time `json:"created_at"`
|
||||
}
|
||||
if err := query.Fields(orderFields).OrderDesc("created_at").Scan(&orders); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
data := make([]map[string]any, len(orders))
|
||||
for i, o := range orders {
|
||||
data[i] = map[string]any{
|
||||
"id": o.Id,
|
||||
"order_no": o.OrderNo,
|
||||
"tenant_id": o.TenantId,
|
||||
"order_type": o.OrderType,
|
||||
"final_amount": o.FinalAmount,
|
||||
"payment_channel": o.PaymentChannel,
|
||||
"status": o.Status,
|
||||
"created_at": o.CreatedAt.String(),
|
||||
}
|
||||
}
|
||||
return nil, export.WriteExcel(r, config, data)
|
||||
}
|
||||
|
||||
return nil, export.StreamCSV(r, config, func(yield func(map[string]any) bool) {
|
||||
return nil, export.GenericExport(ctx, config, func(yield func(map[string]any) bool) {
|
||||
offset := 0
|
||||
for {
|
||||
query := dao.OrdOrders.Ctx(ctx)
|
||||
|
||||
@@ -5,7 +5,6 @@ import (
|
||||
"github.com/qianfree/team-api/internal/dao"
|
||||
do "github.com/qianfree/team-api/internal/model/do"
|
||||
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/os/gtime"
|
||||
|
||||
v1 "github.com/qianfree/team-api/api/admin/v1"
|
||||
@@ -136,15 +135,6 @@ func (s *sAdmin) ToggleRecommend(ctx context.Context, req *v1.PlanToggleRecommen
|
||||
|
||||
// ExportPlans exports plan list to CSV or Excel.
|
||||
func (s *sAdmin) ExportPlans(ctx context.Context, req *v1.PlanExportReq) (*v1.PlanExportRes, error) {
|
||||
r := g.RequestFromCtx(ctx)
|
||||
format := req.Format
|
||||
if format == "" {
|
||||
format = "csv"
|
||||
}
|
||||
if format == "excel" {
|
||||
format = "xlsx"
|
||||
}
|
||||
|
||||
columns := []export.Column{
|
||||
{Field: "id", Header: "ID"},
|
||||
{Field: "name", Header: "名称"},
|
||||
@@ -158,54 +148,14 @@ func (s *sAdmin) ExportPlans(ctx context.Context, req *v1.PlanExportReq) (*v1.Pl
|
||||
}
|
||||
|
||||
config := export.Config{
|
||||
Format: format,
|
||||
Format: req.Format,
|
||||
Filename: "套餐_" + gtime.Now().Format("Ymd_His"),
|
||||
Columns: columns,
|
||||
}
|
||||
|
||||
planFields := "id, name, identifier, monthly_price, yearly_price, status, monthly_quota_tokens, is_recommended, created_at"
|
||||
|
||||
if format == "xlsx" {
|
||||
query := dao.PlnPlans.Ctx(ctx)
|
||||
if req.Status != "" {
|
||||
query = query.Where("status", req.Status)
|
||||
}
|
||||
var plans []struct {
|
||||
Id int64 `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Identifier string `json:"identifier"`
|
||||
MonthlyPrice float64 `json:"monthly_price"`
|
||||
YearlyPrice float64 `json:"yearly_price"`
|
||||
Status string `json:"status"`
|
||||
MonthlyQuotaTokens int64 `json:"monthly_quota_tokens"`
|
||||
IsRecommended bool `json:"is_recommended"`
|
||||
CreatedAt *gtime.Time `json:"created_at"`
|
||||
}
|
||||
if err := query.Fields(planFields).OrderAsc("sort_order").Scan(&plans); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
data := make([]map[string]any, len(plans))
|
||||
for i, p := range plans {
|
||||
isRec := "否"
|
||||
if p.IsRecommended {
|
||||
isRec = "是"
|
||||
}
|
||||
data[i] = map[string]any{
|
||||
"id": p.Id,
|
||||
"name": p.Name,
|
||||
"identifier": p.Identifier,
|
||||
"monthly_price": p.MonthlyPrice,
|
||||
"yearly_price": p.YearlyPrice,
|
||||
"status": p.Status,
|
||||
"monthly_quota_tokens": p.MonthlyQuotaTokens,
|
||||
"is_recommended": isRec,
|
||||
"created_at": p.CreatedAt.String(),
|
||||
}
|
||||
}
|
||||
return nil, export.WriteExcel(r, config, data)
|
||||
}
|
||||
|
||||
return nil, export.StreamCSV(r, config, func(yield func(map[string]any) bool) {
|
||||
return nil, export.GenericExport(ctx, config, func(yield func(map[string]any) bool) {
|
||||
offset := 0
|
||||
for {
|
||||
query := dao.PlnPlans.Ctx(ctx)
|
||||
|
||||
@@ -5,7 +5,6 @@ import (
|
||||
"github.com/qianfree/team-api/internal/dao"
|
||||
"github.com/qianfree/team-api/internal/logic/common"
|
||||
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/os/gtime"
|
||||
|
||||
v1 "github.com/qianfree/team-api/api/admin/v1"
|
||||
@@ -81,15 +80,6 @@ func (s *sAdmin) GetPromoCodeUsages(ctx context.Context, req *v1.PromoCodeUsages
|
||||
|
||||
// ExportPromoCodes exports promo code list to CSV or Excel.
|
||||
func (s *sAdmin) ExportPromoCodes(ctx context.Context, req *v1.PromoCodeExportReq) (*v1.PromoCodeExportRes, error) {
|
||||
r := g.RequestFromCtx(ctx)
|
||||
format := req.Format
|
||||
if format == "" {
|
||||
format = "csv"
|
||||
}
|
||||
if format == "excel" {
|
||||
format = "xlsx"
|
||||
}
|
||||
|
||||
columns := []export.Column{
|
||||
{Field: "id", Header: "ID"},
|
||||
{Field: "code", Header: "优惠码"},
|
||||
@@ -104,48 +94,14 @@ func (s *sAdmin) ExportPromoCodes(ctx context.Context, req *v1.PromoCodeExportRe
|
||||
}
|
||||
|
||||
config := export.Config{
|
||||
Format: format,
|
||||
Format: req.Format,
|
||||
Filename: "优惠码_" + gtime.Now().Format("Ymd_His"),
|
||||
Columns: columns,
|
||||
}
|
||||
|
||||
promoFields := "id, code, name, type, discount_value, min_amount, total_count, used_count, status, created_at"
|
||||
|
||||
if format == "xlsx" {
|
||||
var items []struct {
|
||||
Id int64 `json:"id"`
|
||||
Code string `json:"code"`
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"`
|
||||
DiscountValue float64 `json:"discount_value"`
|
||||
MinAmount float64 `json:"min_amount"`
|
||||
TotalCount int `json:"total_count"`
|
||||
UsedCount int `json:"used_count"`
|
||||
Status string `json:"status"`
|
||||
CreatedAt *gtime.Time `json:"created_at"`
|
||||
}
|
||||
if err := dao.OrdPromoCodes.Ctx(ctx).Fields(promoFields).OrderDesc("created_at").Scan(&items); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
data := make([]map[string]any, len(items))
|
||||
for i, item := range items {
|
||||
data[i] = map[string]any{
|
||||
"id": item.Id,
|
||||
"code": item.Code,
|
||||
"name": item.Name,
|
||||
"type": item.Type,
|
||||
"discount_value": item.DiscountValue,
|
||||
"min_amount": item.MinAmount,
|
||||
"total_count": item.TotalCount,
|
||||
"used_count": item.UsedCount,
|
||||
"status": item.Status,
|
||||
"created_at": item.CreatedAt.String(),
|
||||
}
|
||||
}
|
||||
return nil, export.WriteExcel(r, config, data)
|
||||
}
|
||||
|
||||
return nil, export.StreamCSV(r, config, func(yield func(map[string]any) bool) {
|
||||
return nil, export.GenericExport(ctx, config, func(yield func(map[string]any) bool) {
|
||||
offset := 0
|
||||
for {
|
||||
var batch []struct {
|
||||
|
||||
@@ -183,15 +183,6 @@ func (s *sAdmin) ListRedemptionUsages(ctx context.Context, req *v1.RedemptionUsa
|
||||
|
||||
// ExportRedemptions exports redemption list to CSV or Excel.
|
||||
func (s *sAdmin) ExportRedemptions(ctx context.Context, req *v1.RedemptionExportReq) (*v1.RedemptionExportRes, error) {
|
||||
r := g.RequestFromCtx(ctx)
|
||||
format := req.Format
|
||||
if format == "" {
|
||||
format = "csv"
|
||||
}
|
||||
if format == "excel" {
|
||||
format = "xlsx"
|
||||
}
|
||||
|
||||
columns := []export.Column{
|
||||
{Field: "id", Header: "ID"},
|
||||
{Field: "code", Header: "兑换码"},
|
||||
@@ -204,48 +195,14 @@ func (s *sAdmin) ExportRedemptions(ctx context.Context, req *v1.RedemptionExport
|
||||
}
|
||||
|
||||
config := export.Config{
|
||||
Format: format,
|
||||
Format: req.Format,
|
||||
Filename: "兑换码_" + gtime.Now().Format("Ymd_His"),
|
||||
Columns: columns,
|
||||
}
|
||||
|
||||
redemptionFields := "id, code, type, value, used_count, status, batch_no, created_at"
|
||||
|
||||
if format == "xlsx" {
|
||||
query := dao.OrdRedemptions.Ctx(ctx)
|
||||
if req.Status != "" {
|
||||
query = query.Where("status", req.Status)
|
||||
}
|
||||
var items []struct {
|
||||
Id int64 `json:"id"`
|
||||
Code string `json:"code"`
|
||||
Type string `json:"type"`
|
||||
Value float64 `json:"value"`
|
||||
UsedCount int `json:"used_count"`
|
||||
Status string `json:"status"`
|
||||
BatchNo string `json:"batch_no"`
|
||||
CreatedAt *gtime.Time `json:"created_at"`
|
||||
}
|
||||
if err := query.Fields(redemptionFields).OrderDesc("created_at").Scan(&items); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
data := make([]map[string]any, len(items))
|
||||
for i, item := range items {
|
||||
data[i] = map[string]any{
|
||||
"id": item.Id,
|
||||
"code": item.Code,
|
||||
"type": item.Type,
|
||||
"value": item.Value,
|
||||
"used_count": item.UsedCount,
|
||||
"status": item.Status,
|
||||
"batch_no": item.BatchNo,
|
||||
"created_at": item.CreatedAt.String(),
|
||||
}
|
||||
}
|
||||
return nil, export.WriteExcel(r, config, data)
|
||||
}
|
||||
|
||||
return nil, export.StreamCSV(r, config, func(yield func(map[string]any) bool) {
|
||||
return nil, export.GenericExport(ctx, config, func(yield func(map[string]any) bool) {
|
||||
offset := 0
|
||||
for {
|
||||
query := dao.OrdRedemptions.Ctx(ctx)
|
||||
|
||||
@@ -99,7 +99,7 @@ func (s *sAdmin) Verify2FA(ctx context.Context, req *v1.Admin2FAVerifyReq) (*v1.
|
||||
|
||||
// Setup2FA starts the 2FA setup process for the current admin user.
|
||||
func (s *sAdmin) Setup2FA(ctx context.Context, _ *v1.Admin2FASetupReq) (*v1.Admin2FASetupRes, error) {
|
||||
userID := ctxUserID(ctx)
|
||||
userID := common.GetCtxUserID(ctx)
|
||||
secret, uri, err := common.Setup2FA(ctx, "admin", userID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -122,7 +122,7 @@ func (s *sAdmin) Setup2FA(ctx context.Context, _ *v1.Admin2FASetupReq) (*v1.Admi
|
||||
|
||||
// Enable2FA confirms and enables 2FA after verifying the code.
|
||||
func (s *sAdmin) Enable2FA(ctx context.Context, req *v1.Admin2FAEnableReq) (*v1.Admin2FAEnableRes, error) {
|
||||
userID := ctxUserID(ctx)
|
||||
userID := common.GetCtxUserID(ctx)
|
||||
|
||||
// Get pending secret from session/cache (stored temporarily during setup)
|
||||
secret, err := getPendingTOTPSecret(ctx, userID)
|
||||
@@ -143,7 +143,7 @@ func (s *sAdmin) Enable2FA(ctx context.Context, req *v1.Admin2FAEnableReq) (*v1.
|
||||
|
||||
// Disable2FA disables 2FA for the current admin user.
|
||||
func (s *sAdmin) Disable2FA(ctx context.Context, req *v1.Admin2FADisableReq) (*v1.Admin2FADisableRes, error) {
|
||||
userID := ctxUserID(ctx)
|
||||
userID := common.GetCtxUserID(ctx)
|
||||
err := common.Disable2FA(ctx, "admin", userID, req.Code)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -153,7 +153,7 @@ func (s *sAdmin) Disable2FA(ctx context.Context, req *v1.Admin2FADisableReq) (*v
|
||||
|
||||
// RegenerateBackupCodes generates new backup codes.
|
||||
func (s *sAdmin) RegenerateBackupCodes(ctx context.Context, req *v1.Admin2FARegenerateBackupCodesReq) (*v1.Admin2FARegenerateBackupCodesRes, error) {
|
||||
userID := ctxUserID(ctx)
|
||||
userID := common.GetCtxUserID(ctx)
|
||||
codes, err := common.RegenerateBackupCodes(ctx, "admin", userID, req.Code)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -163,7 +163,7 @@ func (s *sAdmin) RegenerateBackupCodes(ctx context.Context, req *v1.Admin2FARege
|
||||
|
||||
// ConfirmHighRisk generates a confirm token for high-risk operations.
|
||||
func (s *sAdmin) ConfirmHighRisk(ctx context.Context, req *v1.Admin2FAConfirmReq) (*v1.Admin2FAConfirmRes, error) {
|
||||
userID := ctxUserID(ctx)
|
||||
userID := common.GetCtxUserID(ctx)
|
||||
|
||||
enabled, err := common.Is2FAEnabled(ctx, "admin", userID)
|
||||
if err != nil {
|
||||
|
||||
@@ -6,7 +6,6 @@ import (
|
||||
|
||||
"github.com/gogf/gf/v2/database/gdb"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/os/gtime"
|
||||
|
||||
v1 "github.com/qianfree/team-api/api/admin/v1"
|
||||
@@ -444,15 +443,6 @@ func (s *sAdmin) UpdateTenantChannelScope(ctx context.Context, req *v1.TenantCha
|
||||
|
||||
// ExportTenants exports tenant list to CSV or Excel.
|
||||
func (s *sAdmin) ExportTenants(ctx context.Context, req *v1.TenantExportReq) (*v1.TenantExportRes, error) {
|
||||
r := g.RequestFromCtx(ctx)
|
||||
format := req.Format
|
||||
if format == "" {
|
||||
format = "csv"
|
||||
}
|
||||
if format == "excel" {
|
||||
format = "xlsx"
|
||||
}
|
||||
|
||||
columns := []export.Column{
|
||||
{Field: "id", Header: "ID"},
|
||||
{Field: "name", Header: "名称"},
|
||||
@@ -465,20 +455,18 @@ func (s *sAdmin) ExportTenants(ctx context.Context, req *v1.TenantExportReq) (*v
|
||||
}
|
||||
|
||||
config := export.Config{
|
||||
Format: format,
|
||||
Format: req.Format,
|
||||
Filename: "租户_" + gtime.Now().Format("Ymd_His"),
|
||||
Columns: columns,
|
||||
}
|
||||
|
||||
fetchTenantRow := func(t struct {
|
||||
Id int64 `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Code string `json:"code"`
|
||||
OwnerUserID int64 `json:"owner_user_id"`
|
||||
Status string `json:"status"`
|
||||
MaxMembers int `json:"max_members"`
|
||||
MaxConcurrency int `json:"max_concurrency"`
|
||||
CreatedAt *gtime.Time `json:"created_at"`
|
||||
Id int64 `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Code string `json:"code"`
|
||||
OwnerUserID int64 `json:"owner_user_id"`
|
||||
Status string `json:"status"`
|
||||
CreatedAt *gtime.Time `json:"created_at"`
|
||||
}) map[string]any {
|
||||
var owner *struct {
|
||||
DisplayName string `json:"display_name"`
|
||||
@@ -513,36 +501,7 @@ func (s *sAdmin) ExportTenants(ctx context.Context, req *v1.TenantExportReq) (*v
|
||||
}
|
||||
}
|
||||
|
||||
if format == "xlsx" {
|
||||
m := dao.TntTenants.Ctx(ctx)
|
||||
if req.Keyword != "" {
|
||||
keyword := "%" + strings.TrimSpace(req.Keyword) + "%"
|
||||
m = m.Where("name LIKE ? OR code LIKE ?", keyword, keyword)
|
||||
}
|
||||
if req.Status != "" {
|
||||
m = m.Where("status", req.Status)
|
||||
}
|
||||
var tenants []struct {
|
||||
Id int64 `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Code string `json:"code"`
|
||||
OwnerUserID int64 `json:"owner_user_id"`
|
||||
Status string `json:"status"`
|
||||
MaxMembers int `json:"max_members"`
|
||||
MaxConcurrency int `json:"max_concurrency"`
|
||||
CreatedAt *gtime.Time `json:"created_at"`
|
||||
}
|
||||
if err := m.OrderDesc("id").Scan(&tenants); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
data := make([]map[string]any, len(tenants))
|
||||
for i, t := range tenants {
|
||||
data[i] = fetchTenantRow(t)
|
||||
}
|
||||
return nil, export.WriteExcel(r, config, data)
|
||||
}
|
||||
|
||||
return nil, export.StreamCSV(r, config, func(yield func(map[string]any) bool) {
|
||||
return nil, export.GenericExport(ctx, config, func(yield func(map[string]any) bool) {
|
||||
offset := 0
|
||||
for {
|
||||
m := dao.TntTenants.Ctx(ctx)
|
||||
@@ -554,16 +513,14 @@ func (s *sAdmin) ExportTenants(ctx context.Context, req *v1.TenantExportReq) (*v
|
||||
m = m.Where("status", req.Status)
|
||||
}
|
||||
var batch []struct {
|
||||
Id int64 `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Code string `json:"code"`
|
||||
OwnerUserID int64 `json:"owner_user_id"`
|
||||
Status string `json:"status"`
|
||||
MaxMembers int `json:"max_members"`
|
||||
MaxConcurrency int `json:"max_concurrency"`
|
||||
CreatedAt *gtime.Time `json:"created_at"`
|
||||
Id int64 `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Code string `json:"code"`
|
||||
OwnerUserID int64 `json:"owner_user_id"`
|
||||
Status string `json:"status"`
|
||||
CreatedAt *gtime.Time `json:"created_at"`
|
||||
}
|
||||
if err := m.OrderDesc("id").Limit(1000).Offset(offset).Scan(&batch); err != nil {
|
||||
if err := m.Fields("id, name, code, owner_user_id, status, created_at").OrderDesc("id").Limit(1000).Offset(offset).Scan(&batch); err != nil {
|
||||
return
|
||||
}
|
||||
for _, t := range batch {
|
||||
|
||||
@@ -198,7 +198,7 @@ func (s *sAdmin) ReplyToTicketAdmin(ctx context.Context, req *v1.TicketReplyReq)
|
||||
return nil, common.NewBadRequestError("回复内容不能为空")
|
||||
}
|
||||
|
||||
adminID := getCtxUserID(ctx)
|
||||
adminID := common.GetCtxUserID(ctx)
|
||||
|
||||
var ticket *struct {
|
||||
Status string `json:"status"`
|
||||
|
||||
@@ -232,3 +232,29 @@ func CleanExpiredSessions(ctx context.Context) (int64, error) {
|
||||
}
|
||||
return result.RowsAffected()
|
||||
}
|
||||
|
||||
// GetCtxUserID extracts user ID from context.
|
||||
// The key "userId" is set by admin_auth or tenant_auth middleware.
|
||||
func GetCtxUserID(ctx context.Context) int64 {
|
||||
val := ctx.Value("userId")
|
||||
if val == nil {
|
||||
return 0
|
||||
}
|
||||
if id, ok := val.(int64); ok {
|
||||
return id
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// GetCtxSessionID extracts session ID from context.
|
||||
// The key "sessionId" is set by admin_auth or tenant_auth middleware.
|
||||
func GetCtxSessionID(ctx context.Context) int64 {
|
||||
val := ctx.Value("sessionId")
|
||||
if val == nil {
|
||||
return 0
|
||||
}
|
||||
if id, ok := val.(int64); ok {
|
||||
return id
|
||||
}
|
||||
return 0
|
||||
}
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
v1 "github.com/qianfree/team-api/api/open/v1"
|
||||
"github.com/qianfree/team-api/internal/dao"
|
||||
"github.com/qianfree/team-api/internal/logic/relay"
|
||||
"github.com/qianfree/team-api/internal/middleware"
|
||||
do "github.com/qianfree/team-api/internal/model/do"
|
||||
"github.com/qianfree/team-api/internal/service"
|
||||
"github.com/qianfree/team-api/internal/utility/crypto"
|
||||
@@ -26,34 +27,12 @@ func init() {
|
||||
service.RegisterOpen(New())
|
||||
}
|
||||
|
||||
// ctxOpenTenantID extracts tenant_id from context (injected by OpenPlatformAuth middleware).
|
||||
func ctxOpenTenantID(ctx context.Context) int64 {
|
||||
val := ctx.Value("openTenantId")
|
||||
if val != nil {
|
||||
if id, ok := val.(int64); ok {
|
||||
return id
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// ctxOpenAppID extracts app id from context.
|
||||
func ctxOpenAppID(ctx context.Context) int64 {
|
||||
val := ctx.Value("openAppId")
|
||||
if val != nil {
|
||||
if id, ok := val.(int64); ok {
|
||||
return id
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 成员管理
|
||||
// ============================================================
|
||||
|
||||
func (s *sOpen) OpenMemberList(ctx context.Context, req *v1.OpenMemberListReq) (*v1.OpenMemberListRes, error) {
|
||||
tenantID := ctxOpenTenantID(ctx)
|
||||
tenantID := middleware.GetOpenTenantID(ctx)
|
||||
if tenantID == 0 {
|
||||
return nil, fmt.Errorf("unauthorized")
|
||||
}
|
||||
@@ -113,7 +92,7 @@ func (s *sOpen) OpenMemberList(ctx context.Context, req *v1.OpenMemberListReq) (
|
||||
}
|
||||
|
||||
func (s *sOpen) OpenMemberCreate(ctx context.Context, req *v1.OpenMemberCreateReq) (*v1.OpenMemberCreateRes, error) {
|
||||
tenantID := ctxOpenTenantID(ctx)
|
||||
tenantID := middleware.GetOpenTenantID(ctx)
|
||||
if tenantID == 0 {
|
||||
return nil, fmt.Errorf("unauthorized")
|
||||
}
|
||||
@@ -147,7 +126,7 @@ func (s *sOpen) OpenMemberCreate(ctx context.Context, req *v1.OpenMemberCreateRe
|
||||
}
|
||||
|
||||
func (s *sOpen) OpenMemberUpdate(ctx context.Context, req *v1.OpenMemberUpdateReq) (*v1.OpenMemberUpdateRes, error) {
|
||||
tenantID := ctxOpenTenantID(ctx)
|
||||
tenantID := middleware.GetOpenTenantID(ctx)
|
||||
if tenantID == 0 {
|
||||
return nil, fmt.Errorf("unauthorized")
|
||||
}
|
||||
@@ -172,7 +151,7 @@ func (s *sOpen) OpenMemberUpdate(ctx context.Context, req *v1.OpenMemberUpdateRe
|
||||
}
|
||||
|
||||
func (s *sOpen) OpenMemberDelete(ctx context.Context, req *v1.OpenMemberDeleteReq) (*v1.OpenMemberDeleteRes, error) {
|
||||
tenantID := ctxOpenTenantID(ctx)
|
||||
tenantID := middleware.GetOpenTenantID(ctx)
|
||||
if tenantID == 0 {
|
||||
return nil, fmt.Errorf("unauthorized")
|
||||
}
|
||||
@@ -201,7 +180,7 @@ func (s *sOpen) OpenMemberDelete(ctx context.Context, req *v1.OpenMemberDeleteRe
|
||||
// ============================================================
|
||||
|
||||
func (s *sOpen) OpenMemberQuota(ctx context.Context, req *v1.OpenMemberQuotaReq) (*v1.OpenMemberQuotaRes, error) {
|
||||
tenantID := ctxOpenTenantID(ctx)
|
||||
tenantID := middleware.GetOpenTenantID(ctx)
|
||||
if tenantID == 0 {
|
||||
return nil, fmt.Errorf("unauthorized")
|
||||
}
|
||||
@@ -230,7 +209,7 @@ func (s *sOpen) OpenMemberQuota(ctx context.Context, req *v1.OpenMemberQuotaReq)
|
||||
}
|
||||
|
||||
func (s *sOpen) OpenMemberQuotaUpdate(ctx context.Context, req *v1.OpenMemberQuotaUpdateReq) (*v1.OpenMemberQuotaUpdateRes, error) {
|
||||
tenantID := ctxOpenTenantID(ctx)
|
||||
tenantID := middleware.GetOpenTenantID(ctx)
|
||||
if tenantID == 0 {
|
||||
return nil, fmt.Errorf("unauthorized")
|
||||
}
|
||||
@@ -263,7 +242,7 @@ func (s *sOpen) OpenMemberQuotaUpdate(ctx context.Context, req *v1.OpenMemberQuo
|
||||
}
|
||||
|
||||
func (s *sOpen) OpenMemberModels(ctx context.Context, req *v1.OpenMemberModelsReq) (*v1.OpenMemberModelsRes, error) {
|
||||
tenantID := ctxOpenTenantID(ctx)
|
||||
tenantID := middleware.GetOpenTenantID(ctx)
|
||||
if tenantID == 0 {
|
||||
return nil, fmt.Errorf("unauthorized")
|
||||
}
|
||||
@@ -288,7 +267,7 @@ func (s *sOpen) OpenMemberModels(ctx context.Context, req *v1.OpenMemberModelsRe
|
||||
}
|
||||
|
||||
func (s *sOpen) OpenMemberModelsUpdate(ctx context.Context, req *v1.OpenMemberModelsUpdateReq) (*v1.OpenMemberModelsUpdateRes, error) {
|
||||
tenantID := ctxOpenTenantID(ctx)
|
||||
tenantID := middleware.GetOpenTenantID(ctx)
|
||||
if tenantID == 0 {
|
||||
return nil, fmt.Errorf("unauthorized")
|
||||
}
|
||||
@@ -325,7 +304,7 @@ func (s *sOpen) OpenMemberModelsUpdate(ctx context.Context, req *v1.OpenMemberMo
|
||||
// ============================================================
|
||||
|
||||
func (s *sOpen) OpenKeyList(ctx context.Context, req *v1.OpenKeyListReq) (*v1.OpenKeyListRes, error) {
|
||||
tenantID := ctxOpenTenantID(ctx)
|
||||
tenantID := middleware.GetOpenTenantID(ctx)
|
||||
if tenantID == 0 {
|
||||
return nil, fmt.Errorf("unauthorized")
|
||||
}
|
||||
@@ -378,7 +357,7 @@ func (s *sOpen) OpenKeyList(ctx context.Context, req *v1.OpenKeyListReq) (*v1.Op
|
||||
}
|
||||
|
||||
func (s *sOpen) OpenKeyCreate(ctx context.Context, req *v1.OpenKeyCreateReq) (*v1.OpenKeyCreateRes, error) {
|
||||
tenantID := ctxOpenTenantID(ctx)
|
||||
tenantID := middleware.GetOpenTenantID(ctx)
|
||||
if tenantID == 0 {
|
||||
return nil, fmt.Errorf("unauthorized")
|
||||
}
|
||||
@@ -424,7 +403,7 @@ func (s *sOpen) OpenKeyCreate(ctx context.Context, req *v1.OpenKeyCreateReq) (*v
|
||||
}
|
||||
|
||||
func (s *sOpen) OpenKeyDelete(ctx context.Context, req *v1.OpenKeyDeleteReq) (*v1.OpenKeyDeleteRes, error) {
|
||||
tenantID := ctxOpenTenantID(ctx)
|
||||
tenantID := middleware.GetOpenTenantID(ctx)
|
||||
if tenantID == 0 {
|
||||
return nil, fmt.Errorf("unauthorized")
|
||||
}
|
||||
@@ -438,7 +417,7 @@ func (s *sOpen) OpenKeyDelete(ctx context.Context, req *v1.OpenKeyDeleteReq) (*v
|
||||
// ============================================================
|
||||
|
||||
func (s *sOpen) OpenUsageQuery(ctx context.Context, req *v1.OpenUsageQueryReq) (*v1.OpenUsageQueryRes, error) {
|
||||
tenantID := ctxOpenTenantID(ctx)
|
||||
tenantID := middleware.GetOpenTenantID(ctx)
|
||||
if tenantID == 0 {
|
||||
return nil, fmt.Errorf("unauthorized")
|
||||
}
|
||||
@@ -516,7 +495,7 @@ func (s *sOpen) OpenUsageQuery(ctx context.Context, req *v1.OpenUsageQueryReq) (
|
||||
// ============================================================
|
||||
|
||||
func (s *sOpen) OpenBillingQuery(ctx context.Context, req *v1.OpenBillingQueryReq) (*v1.OpenBillingQueryRes, error) {
|
||||
tenantID := ctxOpenTenantID(ctx)
|
||||
tenantID := middleware.GetOpenTenantID(ctx)
|
||||
if tenantID == 0 {
|
||||
return nil, fmt.Errorf("unauthorized")
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ import (
|
||||
"github.com/qianfree/team-api/internal/consts"
|
||||
lcommon "github.com/qianfree/team-api/internal/logic/common"
|
||||
"github.com/qianfree/team-api/internal/logic/relay"
|
||||
"github.com/qianfree/team-api/internal/middleware"
|
||||
do "github.com/qianfree/team-api/internal/model/do"
|
||||
|
||||
v1 "github.com/qianfree/team-api/api/tenant/v1"
|
||||
@@ -20,9 +21,9 @@ import (
|
||||
|
||||
// ApiKeyList 列出 API Keys,支持按类型过滤
|
||||
func (s *sTenant) ApiKeyList(ctx context.Context, req *v1.TenantApiKeyListReq) (*v1.TenantApiKeyListRes, error) {
|
||||
tenantID := ctxTenantID(ctx)
|
||||
userID := ctxUserID(ctx)
|
||||
role := ctxUserRole(ctx)
|
||||
tenantID := middleware.GetTenantID(ctx)
|
||||
userID := middleware.GetUserID(ctx)
|
||||
role := middleware.GetUserRole(ctx)
|
||||
page, pageSize := lcommon.NormalizePagination(req.Page, req.PageSize)
|
||||
|
||||
// member 只能查看个人密钥
|
||||
@@ -111,9 +112,9 @@ func (s *sTenant) ApiKeyList(ctx context.Context, req *v1.TenantApiKeyListReq) (
|
||||
|
||||
// ApiKeyCreate 创建新的 API Key
|
||||
func (s *sTenant) ApiKeyCreate(ctx context.Context, req *v1.TenantApiKeyCreateReq) (*v1.TenantApiKeyCreateRes, error) {
|
||||
tenantID := ctxTenantID(ctx)
|
||||
userID := ctxUserID(ctx)
|
||||
role := ctxUserRole(ctx)
|
||||
tenantID := middleware.GetTenantID(ctx)
|
||||
userID := middleware.GetUserID(ctx)
|
||||
role := middleware.GetUserRole(ctx)
|
||||
|
||||
keyType := req.KeyType
|
||||
if keyType == "" {
|
||||
@@ -204,9 +205,9 @@ func (s *sTenant) ApiKeyCreate(ctx context.Context, req *v1.TenantApiKeyCreateRe
|
||||
|
||||
// ApiKeyDelete 禁用 API Key
|
||||
func (s *sTenant) ApiKeyDelete(ctx context.Context, req *v1.TenantApiKeyDeleteReq) (*v1.TenantApiKeyDeleteRes, error) {
|
||||
tenantID := ctxTenantID(ctx)
|
||||
userID := ctxUserID(ctx)
|
||||
role := ctxUserRole(ctx)
|
||||
tenantID := middleware.GetTenantID(ctx)
|
||||
userID := middleware.GetUserID(ctx)
|
||||
role := middleware.GetUserRole(ctx)
|
||||
keyID := req.Id
|
||||
|
||||
// 先查询密钥信息以判断类型
|
||||
@@ -262,9 +263,9 @@ func (s *sTenant) ApiKeyDelete(ctx context.Context, req *v1.TenantApiKeyDeleteRe
|
||||
|
||||
// ApiKeyUpdate 更新 API Key 的可编辑字段
|
||||
func (s *sTenant) ApiKeyUpdate(ctx context.Context, req *v1.TenantApiKeyUpdateReq) (*v1.TenantApiKeyUpdateRes, error) {
|
||||
tenantID := ctxTenantID(ctx)
|
||||
userID := ctxUserID(ctx)
|
||||
role := ctxUserRole(ctx)
|
||||
tenantID := middleware.GetTenantID(ctx)
|
||||
userID := middleware.GetUserID(ctx)
|
||||
role := middleware.GetUserRole(ctx)
|
||||
keyID := req.Id
|
||||
|
||||
// 查询密钥信息
|
||||
@@ -388,9 +389,9 @@ func (s *sTenant) ApiKeyUpdate(ctx context.Context, req *v1.TenantApiKeyUpdateRe
|
||||
|
||||
// ApiKeyUpdateScopes 更新 API Key 的模型 scope
|
||||
func (s *sTenant) ApiKeyUpdateScopes(ctx context.Context, req *v1.TenantApiKeyUpdateScopesReq) (*v1.TenantApiKeyUpdateScopesRes, error) {
|
||||
tenantID := ctxTenantID(ctx)
|
||||
userID := ctxUserID(ctx)
|
||||
role := ctxUserRole(ctx)
|
||||
tenantID := middleware.GetTenantID(ctx)
|
||||
userID := middleware.GetUserID(ctx)
|
||||
role := middleware.GetUserRole(ctx)
|
||||
keyID := req.Id
|
||||
|
||||
// 先查询密钥信息以判断类型
|
||||
@@ -448,7 +449,7 @@ func (s *sTenant) ApiKeyUpdateScopes(ctx context.Context, req *v1.TenantApiKeyUp
|
||||
|
||||
// ApiKeyModelScopes 查询 API Key 的模型范围
|
||||
func (s *sTenant) ApiKeyModelScopes(ctx context.Context, req *v1.TenantApiKeyModelScopesReq) (*v1.TenantApiKeyModelScopesRes, error) {
|
||||
tenantID := ctxTenantID(ctx)
|
||||
tenantID := middleware.GetTenantID(ctx)
|
||||
|
||||
// 验证 key 属于该租户
|
||||
count, err := dao.ApiKeys.Ctx(ctx).
|
||||
@@ -556,14 +557,8 @@ func listProjectApiKeys(ctx context.Context, tenantID, projectID int64, page, pa
|
||||
|
||||
// ExportApiKeys exports the tenant API key list as CSV or Excel.
|
||||
func (s *sTenant) ExportApiKeys(ctx context.Context, req *v1.TenantApiKeyExportReq) (*v1.TenantApiKeyExportRes, error) {
|
||||
r := g.RequestFromCtx(ctx)
|
||||
format := req.Format
|
||||
if format == "" {
|
||||
format = "csv"
|
||||
}
|
||||
|
||||
tenantID := ctxTenantID(ctx)
|
||||
userID := ctxUserID(ctx)
|
||||
tenantID := middleware.GetTenantID(ctx)
|
||||
userID := middleware.GetUserID(ctx)
|
||||
|
||||
columns := []export.Column{
|
||||
{Field: "id", Header: "ID"},
|
||||
@@ -577,7 +572,7 @@ func (s *sTenant) ExportApiKeys(ctx context.Context, req *v1.TenantApiKeyExportR
|
||||
}
|
||||
|
||||
config := export.Config{
|
||||
Format: format,
|
||||
Format: req.Format,
|
||||
Filename: "API密钥_" + gtime.Now().Format("Ymd_His"),
|
||||
Columns: columns,
|
||||
}
|
||||
@@ -595,44 +590,7 @@ func (s *sTenant) ExportApiKeys(ctx context.Context, req *v1.TenantApiKeyExportR
|
||||
return query
|
||||
}
|
||||
|
||||
if format == "xlsx" {
|
||||
type keyRow struct {
|
||||
Id int64 `json:"id"`
|
||||
Name string `json:"name"`
|
||||
KeyPrefix string `json:"key_prefix"`
|
||||
KeyType string `json:"key_type"`
|
||||
Scope string `json:"scope"`
|
||||
Status string `json:"status"`
|
||||
ExpiresAt *time.Time `json:"expires_at"`
|
||||
CreatedAt *time.Time `json:"created_at"`
|
||||
}
|
||||
|
||||
var keys []keyRow
|
||||
err := buildQuery().
|
||||
Fields("id, name, key_prefix, key_type, scope, status, expires_at, created_at").
|
||||
OrderDesc("created_at").
|
||||
Scan(&keys)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
data := make([]map[string]any, 0, len(keys))
|
||||
for _, k := range keys {
|
||||
data = append(data, map[string]any{
|
||||
"id": k.Id,
|
||||
"name": k.Name,
|
||||
"key_prefix": k.KeyPrefix,
|
||||
"key_type": k.KeyType,
|
||||
"scope": k.Scope,
|
||||
"status": k.Status,
|
||||
"expires_at": k.ExpiresAt,
|
||||
"created_at": k.CreatedAt,
|
||||
})
|
||||
}
|
||||
return nil, export.WriteExcel(r, config, data)
|
||||
}
|
||||
|
||||
return nil, export.StreamCSV(r, config, func(yield func(map[string]any) bool) {
|
||||
return nil, export.GenericExport(ctx, config, func(yield func(map[string]any) bool) {
|
||||
offset := 0
|
||||
for {
|
||||
type keyRow struct {
|
||||
|
||||
@@ -12,6 +12,7 @@ import (
|
||||
"github.com/qianfree/team-api/internal/model/entity"
|
||||
|
||||
"github.com/qianfree/team-api/internal/logic/common"
|
||||
"github.com/qianfree/team-api/internal/middleware"
|
||||
|
||||
v1 "github.com/qianfree/team-api/api/tenant/v1"
|
||||
)
|
||||
@@ -48,11 +49,11 @@ func saveTenantSettings(ctx context.Context, tenantID int64, settings map[string
|
||||
// AuditConfigGet returns the tenant's own audit level.
|
||||
// 租户审计级别与全局级别完全独立,未设置时默认 masked。
|
||||
func (s *sTenant) AuditConfigGet(ctx context.Context, req *v1.TenantAuditConfigGetReq) (*v1.TenantAuditConfigGetRes, error) {
|
||||
role := ctxUserRole(ctx)
|
||||
role := middleware.GetUserRole(ctx)
|
||||
if role != "owner" && role != "admin" {
|
||||
return nil, common.NewForbiddenError("需要 owner 或 admin 权限")
|
||||
}
|
||||
tenantID := ctxTenantID(ctx)
|
||||
tenantID := middleware.GetTenantID(ctx)
|
||||
|
||||
tenantLevel := common.GetTenantAuditLevel(ctx, tenantID)
|
||||
if tenantLevel == "" {
|
||||
@@ -70,7 +71,7 @@ func (s *sTenant) AuditConfigUpdate(ctx context.Context, req *v1.TenantAuditConf
|
||||
if err := ownerOnly(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tenantID := ctxTenantID(ctx)
|
||||
tenantID := middleware.GetTenantID(ctx)
|
||||
|
||||
level := req.AuditLevel
|
||||
|
||||
@@ -94,11 +95,11 @@ func (s *sTenant) AuditConfigUpdate(ctx context.Context, req *v1.TenantAuditConf
|
||||
|
||||
// AuditLogs returns a paginated list of audit logs for the tenant.
|
||||
func (s *sTenant) AuditLogs(ctx context.Context, req *v1.TenantAuditLogsReq) (*v1.TenantAuditLogsRes, error) {
|
||||
role := ctxUserRole(ctx)
|
||||
role := middleware.GetUserRole(ctx)
|
||||
if role != "owner" && role != "admin" {
|
||||
return nil, common.NewForbiddenError("需要 owner 或 admin 权限")
|
||||
}
|
||||
tenantID := ctxTenantID(ctx)
|
||||
tenantID := middleware.GetTenantID(ctx)
|
||||
|
||||
page, pageSize := common.NormalizePagination(req.Page, req.PageSize)
|
||||
|
||||
@@ -152,11 +153,11 @@ func isValidAuditLevel(level string) bool {
|
||||
|
||||
// TenantRequestAuditLogs 分页查询租户的请求审计日志(不含 body,性能优先)
|
||||
func (s *sTenant) TenantRequestAuditLogs(ctx context.Context, req *v1.TenantRequestAuditLogsReq) (*v1.TenantRequestAuditLogsRes, error) {
|
||||
role := ctxUserRole(ctx)
|
||||
role := middleware.GetUserRole(ctx)
|
||||
if role != "owner" && role != "admin" {
|
||||
return nil, common.NewForbiddenError("需要 owner 或 admin 权限")
|
||||
}
|
||||
tenantID := ctxTenantID(ctx)
|
||||
tenantID := middleware.GetTenantID(ctx)
|
||||
page, pageSize := common.NormalizePagination(req.Page, req.PageSize)
|
||||
|
||||
var conditions []string
|
||||
@@ -236,11 +237,11 @@ func (s *sTenant) TenantRequestAuditLogs(ctx context.Context, req *v1.TenantRequ
|
||||
|
||||
// TenantRequestAuditLogDetail 查询单条请求审计日志详情(含 request_body 和 response_body)
|
||||
func (s *sTenant) TenantRequestAuditLogDetail(ctx context.Context, req *v1.TenantRequestAuditLogDetailReq) (*v1.TenantRequestAuditLogDetailRes, error) {
|
||||
role := ctxUserRole(ctx)
|
||||
role := middleware.GetUserRole(ctx)
|
||||
if role != "owner" && role != "admin" {
|
||||
return nil, common.NewForbiddenError("需要 owner 或 admin 权限")
|
||||
}
|
||||
tenantID := ctxTenantID(ctx)
|
||||
tenantID := middleware.GetTenantID(ctx)
|
||||
|
||||
var record *entity.AudRequestLogs
|
||||
err := dao.AudRequestLogs.Ctx(ctx).
|
||||
|
||||
@@ -19,6 +19,7 @@ import (
|
||||
"github.com/qianfree/team-api/internal/consts"
|
||||
"github.com/qianfree/team-api/internal/dao"
|
||||
"github.com/qianfree/team-api/internal/logic/common"
|
||||
"github.com/qianfree/team-api/internal/middleware"
|
||||
"github.com/qianfree/team-api/internal/model/entity"
|
||||
"github.com/qianfree/team-api/internal/utility/crypto"
|
||||
)
|
||||
@@ -388,7 +389,7 @@ func (s *sTenant) Login(ctx context.Context, req *v1.TenantLoginReq) (*v1.Tenant
|
||||
|
||||
// Logout handles tenant user logout.
|
||||
func (s *sTenant) Logout(ctx context.Context, req *v1.TenantLogoutReq) (*v1.TenantLogoutRes, error) {
|
||||
sessionID := ctxSessionID(ctx)
|
||||
sessionID := middleware.GetSessionID(ctx)
|
||||
common.MarkSessionRevoked(ctx, sessionID)
|
||||
err := common.RevokeSession(ctx, sessionID)
|
||||
if err != nil {
|
||||
@@ -452,7 +453,7 @@ func (s *sTenant) Refresh(ctx context.Context, req *v1.TenantRefreshReq) (*v1.Te
|
||||
|
||||
// ChangePassword handles tenant user password change.
|
||||
func (s *sTenant) ChangePassword(ctx context.Context, req *v1.TenantChangePasswordReq) (*v1.TenantChangePasswordRes, error) {
|
||||
userID := ctxUserID(ctx)
|
||||
userID := middleware.GetUserID(ctx)
|
||||
|
||||
var user *entity.TntUsers
|
||||
err := dao.TntUsers.Ctx(ctx).
|
||||
@@ -492,8 +493,8 @@ func (s *sTenant) ChangePassword(ctx context.Context, req *v1.TenantChangePasswo
|
||||
|
||||
// ListSessions returns active sessions for the current tenant user.
|
||||
func (s *sTenant) ListSessions(ctx context.Context, req *v1.TenantSessionListReq) (*v1.TenantSessionListRes, error) {
|
||||
userID := ctxUserID(ctx)
|
||||
currentSessionID := ctxSessionID(ctx)
|
||||
userID := middleware.GetUserID(ctx)
|
||||
currentSessionID := middleware.GetSessionID(ctx)
|
||||
|
||||
sessions, err := common.ListSessions(ctx, "tenant", userID)
|
||||
if err != nil {
|
||||
|
||||
@@ -14,16 +14,17 @@ import (
|
||||
"github.com/gogf/gf/v2/os/gtime"
|
||||
|
||||
v1 "github.com/qianfree/team-api/api/tenant/v1"
|
||||
"github.com/qianfree/team-api/internal/middleware"
|
||||
"github.com/qianfree/team-api/internal/utility/export"
|
||||
)
|
||||
|
||||
// Wallet 获取租户钱包余额
|
||||
func (s *sTenant) Wallet(ctx context.Context, req *v1.TenantWalletReq) (*v1.TenantWalletRes, error) {
|
||||
role := ctxUserRole(ctx)
|
||||
role := middleware.GetUserRole(ctx)
|
||||
if role != "owner" && role != "admin" {
|
||||
return nil, common.NewForbiddenError("需要 owner 或 admin 权限")
|
||||
}
|
||||
tenantID := ctxTenantID(ctx)
|
||||
tenantID := middleware.GetTenantID(ctx)
|
||||
|
||||
type walletRow struct {
|
||||
Balance float64 `json:"balance"`
|
||||
@@ -74,11 +75,11 @@ func (s *sTenant) Wallet(ctx context.Context, req *v1.TenantWalletReq) (*v1.Tena
|
||||
|
||||
// WalletTransactions 获取租户钱包流水
|
||||
func (s *sTenant) WalletTransactions(ctx context.Context, req *v1.TenantWalletTransactionsReq) (*v1.TenantWalletTransactionsRes, error) {
|
||||
role := ctxUserRole(ctx)
|
||||
role := middleware.GetUserRole(ctx)
|
||||
if role != "owner" && role != "admin" {
|
||||
return nil, common.NewForbiddenError("需要 owner 或 admin 权限")
|
||||
}
|
||||
tenantID := ctxTenantID(ctx)
|
||||
tenantID := middleware.GetTenantID(ctx)
|
||||
page, pageSize := common.NormalizePagination(req.Page, req.PageSize)
|
||||
|
||||
query := dao.BilTransactions.Ctx(ctx).
|
||||
@@ -151,9 +152,9 @@ func (s *sTenant) WalletTransactions(ctx context.Context, req *v1.TenantWalletTr
|
||||
|
||||
// UsageLogs 获取租户用量日志
|
||||
func (s *sTenant) UsageLogs(ctx context.Context, req *v1.TenantUsageLogsReq) (*v1.TenantUsageLogsRes, error) {
|
||||
tenantID := ctxTenantID(ctx)
|
||||
userID := ctxUserID(ctx)
|
||||
role := ctxUserRole(ctx)
|
||||
tenantID := middleware.GetTenantID(ctx)
|
||||
userID := middleware.GetUserID(ctx)
|
||||
role := middleware.GetUserRole(ctx)
|
||||
page, pageSize := common.NormalizePagination(req.Page, req.PageSize)
|
||||
|
||||
var conditions []string
|
||||
@@ -243,15 +244,9 @@ func (s *sTenant) UsageLogs(ctx context.Context, req *v1.TenantUsageLogsReq) (*v
|
||||
|
||||
// ExportUsageLogs exports the tenant usage logs as CSV or Excel.
|
||||
func (s *sTenant) ExportUsageLogs(ctx context.Context, req *v1.TenantUsageLogsExportReq) (*v1.TenantUsageLogsExportRes, error) {
|
||||
r := g.RequestFromCtx(ctx)
|
||||
format := req.Format
|
||||
if format == "" {
|
||||
format = "csv"
|
||||
}
|
||||
|
||||
tenantID := ctxTenantID(ctx)
|
||||
userID := ctxUserID(ctx)
|
||||
role := ctxUserRole(ctx)
|
||||
tenantID := middleware.GetTenantID(ctx)
|
||||
userID := middleware.GetUserID(ctx)
|
||||
role := middleware.GetUserRole(ctx)
|
||||
|
||||
columns := []export.Column{
|
||||
{Field: "id", Header: "ID"},
|
||||
@@ -266,7 +261,7 @@ func (s *sTenant) ExportUsageLogs(ctx context.Context, req *v1.TenantUsageLogsEx
|
||||
}
|
||||
|
||||
config := export.Config{
|
||||
Format: format,
|
||||
Format: req.Format,
|
||||
Filename: "用量日志_" + gtime.Now().Format("Ymd_His"),
|
||||
Columns: columns,
|
||||
}
|
||||
@@ -309,40 +304,7 @@ func (s *sTenant) ExportUsageLogs(ctx context.Context, req *v1.TenantUsageLogsEx
|
||||
where := strings.Join(conditions, " AND ")
|
||||
fromClause := "bil_usage_logs u LEFT JOIN tnt_users t ON u.user_id = t.id AND u.tenant_id = t.tenant_id LEFT JOIN tnt_projects p ON u.project_id = p.id LEFT JOIN api_keys ak ON u.api_key_id = ak.id"
|
||||
|
||||
if format == "xlsx" {
|
||||
dataSQL := fmt.Sprintf(
|
||||
`SELECT u.id, COALESCE(t.username, '') AS username, u.model_name, u.request_type,
|
||||
u.input_tokens, u.output_tokens, u.total_cost, u.status, u.created_at
|
||||
FROM %s WHERE %s ORDER BY u.created_at DESC`,
|
||||
fromClause, where,
|
||||
)
|
||||
result, err := g.DB().Ctx(ctx).Query(ctx, dataSQL, args...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
data := make([]map[string]any, 0, len(result))
|
||||
for _, row := range result {
|
||||
m := make(map[string]any, len(row))
|
||||
for k, v := range row {
|
||||
switch raw := v.Val().(type) {
|
||||
case []byte:
|
||||
s := string(raw)
|
||||
if f, err := strconv.ParseFloat(s, 64); err == nil {
|
||||
m[k] = f
|
||||
} else {
|
||||
m[k] = s
|
||||
}
|
||||
default:
|
||||
m[k] = raw
|
||||
}
|
||||
}
|
||||
data = append(data, m)
|
||||
}
|
||||
return nil, export.WriteExcel(r, config, data)
|
||||
}
|
||||
|
||||
return nil, export.StreamCSV(r, config, func(yield func(map[string]any) bool) {
|
||||
return nil, export.GenericExport(ctx, config, func(yield func(map[string]any) bool) {
|
||||
offset := 0
|
||||
for {
|
||||
dataSQL := fmt.Sprintf(
|
||||
@@ -384,18 +346,12 @@ func (s *sTenant) ExportUsageLogs(ctx context.Context, req *v1.TenantUsageLogsEx
|
||||
|
||||
// ExportWalletTransactions exports the tenant wallet transactions as CSV or Excel.
|
||||
func (s *sTenant) ExportWalletTransactions(ctx context.Context, req *v1.TenantWalletTransactionsExportReq) (*v1.TenantWalletTransactionsExportRes, error) {
|
||||
role := ctxUserRole(ctx)
|
||||
role := middleware.GetUserRole(ctx)
|
||||
if role != "owner" && role != "admin" {
|
||||
return nil, common.NewForbiddenError("需要 owner 或 admin 权限")
|
||||
}
|
||||
|
||||
r := g.RequestFromCtx(ctx)
|
||||
format := req.Format
|
||||
if format == "" {
|
||||
format = "csv"
|
||||
}
|
||||
|
||||
tenantID := ctxTenantID(ctx)
|
||||
tenantID := middleware.GetTenantID(ctx)
|
||||
|
||||
columns := []export.Column{
|
||||
{Field: "id", Header: "ID"},
|
||||
@@ -407,46 +363,12 @@ func (s *sTenant) ExportWalletTransactions(ctx context.Context, req *v1.TenantWa
|
||||
}
|
||||
|
||||
config := export.Config{
|
||||
Format: format,
|
||||
Format: req.Format,
|
||||
Filename: "交易记录_" + gtime.Now().Format("Ymd_His"),
|
||||
Columns: columns,
|
||||
}
|
||||
|
||||
if format == "xlsx" {
|
||||
type transactionRow struct {
|
||||
Id int64 `json:"id"`
|
||||
Type string `json:"type"`
|
||||
Amount float64 `json:"amount"`
|
||||
BalanceAfter float64 `json:"balance_after"`
|
||||
Description string `json:"description"`
|
||||
CreatedAt *gtime.Time `json:"created_at"`
|
||||
}
|
||||
|
||||
var records []transactionRow
|
||||
err := dao.BilTransactions.Ctx(ctx).
|
||||
Where("tenant_id", tenantID).
|
||||
Fields("id, type, amount, balance_after, description, created_at").
|
||||
OrderDesc("created_at").
|
||||
Scan(&records)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
data := make([]map[string]any, 0, len(records))
|
||||
for _, r := range records {
|
||||
data = append(data, map[string]any{
|
||||
"id": r.Id,
|
||||
"type": r.Type,
|
||||
"amount": r.Amount,
|
||||
"balance_after": r.BalanceAfter,
|
||||
"description": r.Description,
|
||||
"created_at": r.CreatedAt,
|
||||
})
|
||||
}
|
||||
return nil, export.WriteExcel(r, config, data)
|
||||
}
|
||||
|
||||
return nil, export.StreamCSV(r, config, func(yield func(map[string]any) bool) {
|
||||
return nil, export.GenericExport(ctx, config, func(yield func(map[string]any) bool) {
|
||||
offset := 0
|
||||
for {
|
||||
type transactionRow struct {
|
||||
@@ -493,11 +415,11 @@ func (s *sTenant) ExportWalletTransactions(ctx context.Context, req *v1.TenantWa
|
||||
|
||||
// WalletFrozenItems 获取冻结明细
|
||||
func (s *sTenant) WalletFrozenItems(ctx context.Context, req *v1.TenantWalletFrozenItemsReq) (*v1.TenantWalletFrozenItemsRes, error) {
|
||||
role := ctxUserRole(ctx)
|
||||
role := middleware.GetUserRole(ctx)
|
||||
if role != "owner" && role != "admin" {
|
||||
return nil, common.NewForbiddenError("需要 owner 或 admin 权限")
|
||||
}
|
||||
tenantID := ctxTenantID(ctx)
|
||||
tenantID := middleware.GetTenantID(ctx)
|
||||
|
||||
items, err := billing.GetFrozenItems(ctx, tenantID)
|
||||
if err != nil {
|
||||
|
||||
@@ -11,15 +11,16 @@ import (
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
|
||||
v1 "github.com/qianfree/team-api/api/tenant/v1"
|
||||
"github.com/qianfree/team-api/internal/middleware"
|
||||
)
|
||||
|
||||
// Dashboard returns the tenant dashboard statistics.
|
||||
func (s *sTenant) Dashboard(ctx context.Context, req *v1.TenantDashboardReq) (*v1.TenantDashboardRes, error) {
|
||||
role := ctxUserRole(ctx)
|
||||
role := middleware.GetUserRole(ctx)
|
||||
if role != "owner" && role != "admin" {
|
||||
return nil, common.NewForbiddenError("需要 owner 或 admin 权限")
|
||||
}
|
||||
tenantID := ctxTenantID(ctx)
|
||||
tenantID := middleware.GetTenantID(ctx)
|
||||
today := time.Now().Format("2006-01-02")
|
||||
monthStart := time.Now().Format("2006-01") + "-01"
|
||||
|
||||
@@ -102,11 +103,11 @@ func (s *sTenant) Dashboard(ctx context.Context, req *v1.TenantDashboardReq) (*v
|
||||
|
||||
// TokenTrends returns daily token usage for the past N days.
|
||||
func (s *sTenant) TokenTrends(ctx context.Context, req *v1.TenantTokenTrendsReq) (*v1.TenantTokenTrendsRes, error) {
|
||||
role := ctxUserRole(ctx)
|
||||
role := middleware.GetUserRole(ctx)
|
||||
if role != "owner" && role != "admin" {
|
||||
return nil, common.NewForbiddenError("需要 owner 或 admin 权限")
|
||||
}
|
||||
tenantID := ctxTenantID(ctx)
|
||||
tenantID := middleware.GetTenantID(ctx)
|
||||
days := req.Days
|
||||
if days <= 0 || days > 90 {
|
||||
days = 30
|
||||
@@ -160,11 +161,11 @@ func (s *sTenant) TokenTrends(ctx context.Context, req *v1.TenantTokenTrendsReq)
|
||||
|
||||
// ModelDistribution returns the distribution of model usage.
|
||||
func (s *sTenant) ModelDistribution(ctx context.Context, req *v1.TenantModelDistributionReq) (*v1.TenantModelDistributionRes, error) {
|
||||
role := ctxUserRole(ctx)
|
||||
role := middleware.GetUserRole(ctx)
|
||||
if role != "owner" && role != "admin" {
|
||||
return nil, common.NewForbiddenError("需要 owner 或 admin 权限")
|
||||
}
|
||||
tenantID := ctxTenantID(ctx)
|
||||
tenantID := middleware.GetTenantID(ctx)
|
||||
days := req.Days
|
||||
if days <= 0 || days > 90 {
|
||||
days = 30
|
||||
@@ -219,11 +220,11 @@ func (s *sTenant) ModelDistribution(ctx context.Context, req *v1.TenantModelDist
|
||||
|
||||
// BalancePrediction predicts when the balance will be exhausted.
|
||||
func (s *sTenant) BalancePrediction(ctx context.Context, req *v1.TenantBalancePredictionReq) (*v1.TenantBalancePredictionRes, error) {
|
||||
role := ctxUserRole(ctx)
|
||||
role := middleware.GetUserRole(ctx)
|
||||
if role != "owner" && role != "admin" {
|
||||
return nil, common.NewForbiddenError("需要 owner 或 admin 权限")
|
||||
}
|
||||
tenantID := ctxTenantID(ctx)
|
||||
tenantID := middleware.GetTenantID(ctx)
|
||||
|
||||
sevenDaysAgo := time.Now().AddDate(0, 0, -7).Format("2006-01-02")
|
||||
|
||||
@@ -282,7 +283,7 @@ func (s *sTenant) BalancePrediction(ctx context.Context, req *v1.TenantBalancePr
|
||||
|
||||
// BudgetAlerts checks member and project budget usage and returns those above 80%.
|
||||
func (s *sTenant) BudgetAlerts(ctx context.Context, req *v1.TenantBudgetAlertsReq) (*v1.TenantBudgetAlertsRes, error) {
|
||||
role := ctxUserRole(ctx)
|
||||
role := middleware.GetUserRole(ctx)
|
||||
if role != "owner" && role != "admin" {
|
||||
return nil, common.NewForbiddenError("需要 owner 或 admin 权限")
|
||||
}
|
||||
@@ -294,11 +295,11 @@ func (s *sTenant) BudgetAlerts(ctx context.Context, req *v1.TenantBudgetAlertsRe
|
||||
|
||||
// GetMemberUsageRanking returns top members by usage cost in a given date range.
|
||||
func (s *sTenant) GetMemberUsageRanking(ctx context.Context, req *v1.TenantMemberUsageRankingReq) (*v1.TenantMemberUsageRankingRes, error) {
|
||||
role := ctxUserRole(ctx)
|
||||
role := middleware.GetUserRole(ctx)
|
||||
if role != "owner" && role != "admin" {
|
||||
return nil, common.NewForbiddenError("需要 owner 或 admin 权限")
|
||||
}
|
||||
tenantID := ctxTenantID(ctx)
|
||||
tenantID := middleware.GetTenantID(ctx)
|
||||
days := req.Days
|
||||
if days <= 0 || days > 90 {
|
||||
days = 30
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"github.com/qianfree/team-api/internal/consts"
|
||||
"github.com/qianfree/team-api/internal/dao"
|
||||
"github.com/qianfree/team-api/internal/logic/common"
|
||||
"github.com/qianfree/team-api/internal/middleware"
|
||||
"github.com/qianfree/team-api/internal/model/entity"
|
||||
"github.com/qianfree/team-api/internal/utility/crypto"
|
||||
)
|
||||
@@ -102,8 +103,8 @@ func (s *sTenant) ResetPassword(ctx context.Context, req *v1.TenantResetPassword
|
||||
|
||||
// ChangeEmail handles email change for a tenant user.
|
||||
func (s *sTenant) ChangeEmail(ctx context.Context, req *v1.TenantChangeEmailReq) (*v1.TenantChangeEmailRes, error) {
|
||||
tenantID := ctxTenantID(ctx)
|
||||
userID := ctxUserID(ctx)
|
||||
tenantID := middleware.GetTenantID(ctx)
|
||||
userID := middleware.GetUserID(ctx)
|
||||
newEmail := strings.TrimSpace(strings.ToLower(req.NewEmail))
|
||||
|
||||
// Verify code for new email
|
||||
|
||||
@@ -6,14 +6,15 @@ import (
|
||||
|
||||
v1 "github.com/qianfree/team-api/api/tenant/v1"
|
||||
"github.com/qianfree/team-api/internal/logic/common"
|
||||
"github.com/qianfree/team-api/internal/middleware"
|
||||
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
)
|
||||
|
||||
// CreateFeedback 提交反馈
|
||||
func (s *sTenant) CreateFeedback(ctx context.Context, req *v1.FeedbackCreateReq) (*v1.FeedbackCreateRes, error) {
|
||||
tenantID := ctxTenantID(ctx)
|
||||
userID := ctxUserID(ctx)
|
||||
tenantID := middleware.GetTenantID(ctx)
|
||||
userID := middleware.GetUserID(ctx)
|
||||
|
||||
metadata := "{}"
|
||||
if req.Metadata != nil {
|
||||
@@ -40,8 +41,8 @@ func (s *sTenant) CreateFeedback(ctx context.Context, req *v1.FeedbackCreateReq)
|
||||
|
||||
// ListFeedbacks 我的反馈列表
|
||||
func (s *sTenant) ListFeedbacks(ctx context.Context, req *v1.FeedbackListReq) (*v1.FeedbackListRes, error) {
|
||||
tenantID := ctxTenantID(ctx)
|
||||
userID := ctxUserID(ctx)
|
||||
tenantID := middleware.GetTenantID(ctx)
|
||||
userID := middleware.GetUserID(ctx)
|
||||
page, pageSize := normalizePagination(req.Page, req.PageSize)
|
||||
|
||||
query := g.DB().Model("spt_feedbacks").Ctx(ctx).
|
||||
@@ -73,8 +74,8 @@ func (s *sTenant) ListFeedbacks(ctx context.Context, req *v1.FeedbackListReq) (*
|
||||
|
||||
// GetFeedback 反馈详情
|
||||
func (s *sTenant) GetFeedback(ctx context.Context, req *v1.FeedbackGetReq) (*v1.FeedbackGetRes, error) {
|
||||
tenantID := ctxTenantID(ctx)
|
||||
userID := ctxUserID(ctx)
|
||||
tenantID := middleware.GetTenantID(ctx)
|
||||
userID := middleware.GetUserID(ctx)
|
||||
|
||||
var row v1.FeedbackGetRes
|
||||
err := g.DB().Model("spt_feedbacks").Ctx(ctx).
|
||||
|
||||
@@ -7,13 +7,14 @@ import (
|
||||
v1 "github.com/qianfree/team-api/api/tenant/v1"
|
||||
"github.com/qianfree/team-api/internal/dao"
|
||||
"github.com/qianfree/team-api/internal/logic/common"
|
||||
"github.com/qianfree/team-api/internal/middleware"
|
||||
|
||||
"github.com/gogf/gf/v2/os/gtime"
|
||||
)
|
||||
|
||||
// InvitationList returns a paginated list of invitation records for the tenant.
|
||||
func (s *sTenant) InvitationList(ctx context.Context, req *v1.TenantInvitationListReq) (*v1.TenantInvitationListRes, error) {
|
||||
tenantID := ctxTenantID(ctx)
|
||||
tenantID := middleware.GetTenantID(ctx)
|
||||
page, pageSize := common.NormalizePagination(req.Page, req.PageSize)
|
||||
|
||||
type row struct {
|
||||
@@ -102,7 +103,7 @@ func (s *sTenant) InvitationList(ctx context.Context, req *v1.TenantInvitationLi
|
||||
|
||||
// RevokeInvitation revokes a pending invitation by setting used_by_user_id = -1.
|
||||
func (s *sTenant) RevokeInvitation(ctx context.Context, req *v1.TenantInvitationRevokeReq) (*v1.TenantInvitationRevokeRes, error) {
|
||||
tenantID := ctxTenantID(ctx)
|
||||
tenantID := middleware.GetTenantID(ctx)
|
||||
|
||||
var inv struct {
|
||||
ID int64 `json:"id"`
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
"github.com/gogf/gf/v2/os/gtime"
|
||||
|
||||
v1 "github.com/qianfree/team-api/api/tenant/v1"
|
||||
"github.com/qianfree/team-api/internal/middleware"
|
||||
)
|
||||
|
||||
// 合法状态转换映射
|
||||
@@ -72,7 +73,7 @@ func TransitionTenantStatus(ctx context.Context, tenantID int64, newStatus strin
|
||||
|
||||
// RequestClosure 申请关户
|
||||
func (s *sTenant) RequestClosure(ctx context.Context, req *v1.TenantRequestClosureReq) (*v1.TenantRequestClosureRes, error) {
|
||||
tenantID := ctxTenantID(ctx)
|
||||
tenantID := middleware.GetTenantID(ctx)
|
||||
if err := TransitionTenantStatus(ctx, tenantID, "closing"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -81,7 +82,7 @@ func (s *sTenant) RequestClosure(ctx context.Context, req *v1.TenantRequestClosu
|
||||
|
||||
// CancelClosure 取消关户
|
||||
func (s *sTenant) CancelClosure(ctx context.Context, req *v1.TenantCancelClosureReq) (*v1.TenantCancelClosureRes, error) {
|
||||
tenantID := ctxTenantID(ctx)
|
||||
tenantID := middleware.GetTenantID(ctx)
|
||||
if err := TransitionTenantStatus(ctx, tenantID, "active"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -18,13 +18,14 @@ import (
|
||||
"github.com/qianfree/team-api/internal/consts"
|
||||
"github.com/qianfree/team-api/internal/dao"
|
||||
"github.com/qianfree/team-api/internal/logic/common"
|
||||
"github.com/qianfree/team-api/internal/middleware"
|
||||
"github.com/qianfree/team-api/internal/utility/crypto"
|
||||
"github.com/qianfree/team-api/internal/utility/export"
|
||||
)
|
||||
|
||||
// ListMembers returns a paginated list of tenant members.
|
||||
func (s *sTenant) ListMembers(ctx context.Context, req *v1.TenantMemberListReq) (*v1.TenantMemberListRes, error) {
|
||||
tenantID := ctxTenantID(ctx)
|
||||
tenantID := middleware.GetTenantID(ctx)
|
||||
|
||||
page, pageSize := common.NormalizePagination(req.Page, req.PageSize)
|
||||
|
||||
@@ -83,8 +84,8 @@ func (s *sTenant) ListMembers(ctx context.Context, req *v1.TenantMemberListReq)
|
||||
|
||||
// InviteMember generates an invitation link.
|
||||
func (s *sTenant) InviteMember(ctx context.Context, req *v1.TenantMemberInviteReq) (*v1.TenantMemberInviteRes, error) {
|
||||
tenantID := ctxTenantID(ctx)
|
||||
creatorID := ctxUserID(ctx)
|
||||
tenantID := middleware.GetTenantID(ctx)
|
||||
creatorID := middleware.GetUserID(ctx)
|
||||
|
||||
// Check member limit
|
||||
memberCount, err := dao.TntUsers.Ctx(ctx).
|
||||
@@ -358,7 +359,7 @@ func (s *sTenant) JoinByInvite(ctx context.Context, req *v1.TenantMemberJoinReq)
|
||||
|
||||
// CreateMember directly creates a member account within the tenant.
|
||||
func (s *sTenant) CreateMember(ctx context.Context, req *v1.TenantMemberCreateReq) (*v1.TenantMemberCreateRes, error) {
|
||||
tenantID := ctxTenantID(ctx)
|
||||
tenantID := middleware.GetTenantID(ctx)
|
||||
|
||||
// Validate role
|
||||
if err := common.ValidateTenantRole(req.Role); err != nil {
|
||||
@@ -464,8 +465,8 @@ func (s *sTenant) CreateMember(ctx context.Context, req *v1.TenantMemberCreateRe
|
||||
// RemoveMember removes a member from the tenant.
|
||||
// Revokes all API keys, anonymizes personal data, releases member model scopes.
|
||||
func (s *sTenant) RemoveMember(ctx context.Context, req *v1.TenantMemberRemoveReq) (*v1.TenantMemberRemoveRes, error) {
|
||||
tenantID := ctxTenantID(ctx)
|
||||
currentUserID := ctxUserID(ctx)
|
||||
tenantID := middleware.GetTenantID(ctx)
|
||||
currentUserID := middleware.GetUserID(ctx)
|
||||
memberID := req.Id
|
||||
|
||||
if memberID == currentUserID {
|
||||
@@ -596,8 +597,8 @@ func (s *sTenant) EnableMember(ctx context.Context, tenantID, userID int64) erro
|
||||
|
||||
// UpdateMemberRole updates a member's role.
|
||||
func (s *sTenant) UpdateMemberRole(ctx context.Context, req *v1.TenantMemberUpdateRoleReq) (*v1.TenantMemberUpdateRoleRes, error) {
|
||||
tenantID := ctxTenantID(ctx)
|
||||
currentUserID := ctxUserID(ctx)
|
||||
tenantID := middleware.GetTenantID(ctx)
|
||||
currentUserID := middleware.GetUserID(ctx)
|
||||
memberID := req.Id
|
||||
|
||||
if memberID == currentUserID {
|
||||
@@ -637,8 +638,8 @@ func (s *sTenant) UpdateMemberRole(ctx context.Context, req *v1.TenantMemberUpda
|
||||
|
||||
// ResetMemberPassword resets a member's password. Only admins can reset other members' passwords.
|
||||
func (s *sTenant) ResetMemberPassword(ctx context.Context, req *v1.TenantMemberResetPasswordReq) (*v1.TenantMemberResetPasswordRes, error) {
|
||||
tenantID := ctxTenantID(ctx)
|
||||
currentUserID := ctxUserID(ctx)
|
||||
tenantID := middleware.GetTenantID(ctx)
|
||||
currentUserID := middleware.GetUserID(ctx)
|
||||
memberID := req.Id
|
||||
|
||||
if memberID == currentUserID {
|
||||
@@ -686,7 +687,7 @@ func (s *sTenant) ResetMemberPassword(ctx context.Context, req *v1.TenantMemberR
|
||||
|
||||
// GetMember returns a single member's detail.
|
||||
func (s *sTenant) GetMember(ctx context.Context, req *v1.TenantMemberGetReq) (*v1.TenantMemberGetRes, error) {
|
||||
tenantID := ctxTenantID(ctx)
|
||||
tenantID := middleware.GetTenantID(ctx)
|
||||
|
||||
var user struct {
|
||||
Id int64 `json:"id"`
|
||||
@@ -723,7 +724,7 @@ func (s *sTenant) GetMember(ctx context.Context, req *v1.TenantMemberGetReq) (*v
|
||||
|
||||
// GetMemberUsage returns usage statistics for a single member.
|
||||
func (s *sTenant) GetMemberUsage(ctx context.Context, req *v1.TenantMemberUsageReq) (*v1.TenantMemberUsageRes, error) {
|
||||
tenantID := ctxTenantID(ctx)
|
||||
tenantID := middleware.GetTenantID(ctx)
|
||||
|
||||
// Verify member exists in tenant
|
||||
var user struct {
|
||||
@@ -776,7 +777,7 @@ func (s *sTenant) GetMemberUsage(ctx context.Context, req *v1.TenantMemberUsageR
|
||||
|
||||
// ListMemberApiKeys returns a paginated list of API keys belonging to a specific member.
|
||||
func (s *sTenant) ListMemberApiKeys(ctx context.Context, req *v1.TenantMemberApiKeysReq) (*v1.TenantMemberApiKeysRes, error) {
|
||||
tenantID := ctxTenantID(ctx)
|
||||
tenantID := middleware.GetTenantID(ctx)
|
||||
|
||||
page, pageSize := common.NormalizePagination(req.Page, req.PageSize)
|
||||
|
||||
@@ -843,13 +844,7 @@ func (s *sTenant) ListMemberApiKeys(ctx context.Context, req *v1.TenantMemberApi
|
||||
|
||||
// ExportMembers exports the tenant member list as CSV or Excel.
|
||||
func (s *sTenant) ExportMembers(ctx context.Context, req *v1.TenantMemberExportReq) (*v1.TenantMemberExportRes, error) {
|
||||
r := g.RequestFromCtx(ctx)
|
||||
format := req.Format
|
||||
if format == "" {
|
||||
format = "csv"
|
||||
}
|
||||
|
||||
tenantID := ctxTenantID(ctx)
|
||||
tenantID := middleware.GetTenantID(ctx)
|
||||
|
||||
columns := []export.Column{
|
||||
{Field: "id", Header: "ID"},
|
||||
@@ -862,52 +857,12 @@ func (s *sTenant) ExportMembers(ctx context.Context, req *v1.TenantMemberExportR
|
||||
}
|
||||
|
||||
config := export.Config{
|
||||
Format: format,
|
||||
Format: req.Format,
|
||||
Filename: "成员列表_" + gtime.Now().Format("Ymd_His"),
|
||||
Columns: columns,
|
||||
}
|
||||
|
||||
if format == "xlsx" {
|
||||
model := dao.TntUsers.Ctx(ctx).
|
||||
Where("tenant_id", tenantID)
|
||||
if req.Keyword != "" {
|
||||
keyword := "%" + strings.TrimSpace(req.Keyword) + "%"
|
||||
model = model.Where("username LIKE ? OR email LIKE ?", keyword, keyword)
|
||||
}
|
||||
if req.Role != "" {
|
||||
model = model.Where("role", req.Role)
|
||||
}
|
||||
|
||||
var users []struct {
|
||||
Id int64 `json:"id"`
|
||||
Username string `json:"username"`
|
||||
Email string `json:"email"`
|
||||
DisplayName string `json:"display_name"`
|
||||
Role string `json:"role"`
|
||||
Status string `json:"status"`
|
||||
CreatedAt string `json:"created_at"`
|
||||
}
|
||||
err := model.OrderDesc("id").Scan(&users)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
data := make([]map[string]any, 0, len(users))
|
||||
for _, u := range users {
|
||||
data = append(data, map[string]any{
|
||||
"id": u.Id,
|
||||
"username": u.Username,
|
||||
"email": u.Email,
|
||||
"display_name": u.DisplayName,
|
||||
"role": u.Role,
|
||||
"status": u.Status,
|
||||
"created_at": u.CreatedAt,
|
||||
})
|
||||
}
|
||||
return nil, export.WriteExcel(r, config, data)
|
||||
}
|
||||
|
||||
return nil, export.StreamCSV(r, config, func(yield func(map[string]any) bool) {
|
||||
return nil, export.GenericExport(ctx, config, func(yield func(map[string]any) bool) {
|
||||
offset := 0
|
||||
for {
|
||||
model := dao.TntUsers.Ctx(ctx).
|
||||
|
||||
@@ -16,6 +16,7 @@ import (
|
||||
"github.com/gogf/gf/v2/os/gtime"
|
||||
|
||||
"github.com/qianfree/team-api/internal/logic/common"
|
||||
"github.com/qianfree/team-api/internal/middleware"
|
||||
"github.com/qianfree/team-api/internal/utility/crypto"
|
||||
|
||||
v1 "github.com/qianfree/team-api/api/tenant/v1"
|
||||
@@ -31,8 +32,8 @@ type ImportResult struct {
|
||||
|
||||
// MemberImport parses CSV content, validates, creates an import record.
|
||||
func (s *sTenant) MemberImport(ctx context.Context, req *v1.TenantMemberImportReq) (*v1.TenantMemberImportRes, error) {
|
||||
tenantID := ctxTenantID(ctx)
|
||||
creatorID := ctxUserID(ctx)
|
||||
tenantID := middleware.GetTenantID(ctx)
|
||||
creatorID := middleware.GetUserID(ctx)
|
||||
|
||||
// Get CSV from multipart form file
|
||||
file := g.RequestFromCtx(ctx).GetUploadFile("file")
|
||||
@@ -60,7 +61,7 @@ func (s *sTenant) MemberImport(ctx context.Context, req *v1.TenantMemberImportRe
|
||||
|
||||
// ImportRecords returns a paginated list of import records.
|
||||
func (s *sTenant) ImportRecords(ctx context.Context, req *v1.TenantImportRecordsReq) (*v1.TenantImportRecordsRes, error) {
|
||||
tenantID := ctxTenantID(ctx)
|
||||
tenantID := middleware.GetTenantID(ctx)
|
||||
|
||||
page, pageSize := common.NormalizePagination(req.Page, req.PageSize)
|
||||
|
||||
@@ -117,7 +118,7 @@ func (s *sTenant) ImportRecords(ctx context.Context, req *v1.TenantImportRecords
|
||||
|
||||
// ImportRecordGet returns the status of a member import.
|
||||
func (s *sTenant) ImportRecordGet(ctx context.Context, req *v1.TenantImportRecordGetReq) (*v1.TenantImportRecordGetRes, error) {
|
||||
tenantID := ctxTenantID(ctx)
|
||||
tenantID := middleware.GetTenantID(ctx)
|
||||
|
||||
var record struct {
|
||||
ID int64 `json:"id"`
|
||||
|
||||
@@ -9,11 +9,12 @@ import (
|
||||
do "github.com/qianfree/team-api/internal/model/do"
|
||||
|
||||
v1 "github.com/qianfree/team-api/api/tenant/v1"
|
||||
"github.com/qianfree/team-api/internal/middleware"
|
||||
)
|
||||
|
||||
// MemberModelScopes returns the model IDs available for a member.
|
||||
func (s *sTenant) MemberModelScopes(ctx context.Context, req *v1.TenantMemberModelScopesReq) (*v1.TenantMemberModelScopesRes, error) {
|
||||
tenantID := ctxTenantID(ctx)
|
||||
tenantID := middleware.GetTenantID(ctx)
|
||||
|
||||
var rows []struct {
|
||||
ModelID int64 `json:"model_id"`
|
||||
@@ -35,7 +36,7 @@ func (s *sTenant) MemberModelScopes(ctx context.Context, req *v1.TenantMemberMod
|
||||
|
||||
// MemberModelScopesSet sets the available models for a member (full replace).
|
||||
func (s *sTenant) MemberModelScopesSet(ctx context.Context, req *v1.TenantMemberModelScopesSetReq) (*v1.TenantMemberModelScopesSetRes, error) {
|
||||
tenantID := ctxTenantID(ctx)
|
||||
tenantID := middleware.GetTenantID(ctx)
|
||||
|
||||
err := g.DB().Transaction(ctx, func(ctx context.Context, tx gdb.TX) error {
|
||||
// Delete existing scopes
|
||||
|
||||
@@ -10,10 +10,11 @@ import (
|
||||
|
||||
v1 "github.com/qianfree/team-api/api/tenant/v1"
|
||||
"github.com/qianfree/team-api/internal/dao"
|
||||
"github.com/qianfree/team-api/internal/middleware"
|
||||
)
|
||||
|
||||
func (s *sTenant) MemberQuota(ctx context.Context, req *v1.TenantMemberQuotaReq) (*v1.TenantMemberQuotaRes, error) {
|
||||
tenantID := ctxTenantID(ctx)
|
||||
tenantID := middleware.GetTenantID(ctx)
|
||||
|
||||
var row struct {
|
||||
QuotaType string `json:"quota_type"`
|
||||
@@ -49,7 +50,7 @@ func (s *sTenant) MemberQuota(ctx context.Context, req *v1.TenantMemberQuotaReq)
|
||||
}
|
||||
|
||||
func (s *sTenant) MemberQuotaSet(ctx context.Context, req *v1.TenantMemberQuotaSetReq) (*v1.TenantMemberQuotaSetRes, error) {
|
||||
tenantID := ctxTenantID(ctx)
|
||||
tenantID := middleware.GetTenantID(ctx)
|
||||
|
||||
if req.QuotaType == "periodic" && req.Period == "" {
|
||||
return nil, fmt.Errorf("周期类型不能为空")
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
v1 "github.com/qianfree/team-api/api/tenant/v1"
|
||||
"github.com/qianfree/team-api/internal/dao"
|
||||
"github.com/qianfree/team-api/internal/middleware"
|
||||
)
|
||||
|
||||
// pricingTierRow 阶梯定价行(与 billing.pricingTierRow 结构一致)
|
||||
@@ -19,7 +20,7 @@ type pricingTierRow struct {
|
||||
|
||||
// ListAvailableModels 获取租户可用的模型列表
|
||||
func (s *sTenant) ListAvailableModels(ctx context.Context, req *v1.TenantAvailableModelsReq) (*v1.TenantAvailableModelsRes, error) {
|
||||
tenantID := ctxTenantID(ctx)
|
||||
tenantID := middleware.GetTenantID(ctx)
|
||||
|
||||
query := dao.MdlTenantModels.Ctx(ctx).
|
||||
LeftJoin("mdl_models m ON mdl_tenant_models.model_id = m.id").
|
||||
|
||||
@@ -10,10 +10,11 @@ import (
|
||||
"github.com/gogf/gf/v2/os/gtime"
|
||||
|
||||
v1 "github.com/qianfree/team-api/api/tenant/v1"
|
||||
"github.com/qianfree/team-api/internal/middleware"
|
||||
)
|
||||
|
||||
func (s *sTenant) ModelComparison(ctx context.Context, req *v1.ModelComparisonReq) (*v1.ModelComparisonRes, error) {
|
||||
tenantID := ctxTenantID(ctx)
|
||||
tenantID := middleware.GetTenantID(ctx)
|
||||
|
||||
if req.Days <= 0 {
|
||||
req.Days = 7
|
||||
|
||||
@@ -15,14 +15,15 @@ import (
|
||||
v1 "github.com/qianfree/team-api/api/tenant/v1"
|
||||
|
||||
"github.com/qianfree/team-api/internal/logic/common"
|
||||
"github.com/qianfree/team-api/internal/middleware"
|
||||
"github.com/qianfree/team-api/internal/utility/export"
|
||||
)
|
||||
|
||||
// Notifications 获取用户的通知列表(个人消息 + 广播消息)
|
||||
func (s *sTenant) Notifications(ctx context.Context, req *v1.TenantNotificationsReq) (*v1.TenantNotificationsRes, error) {
|
||||
tenantID := ctxTenantID(ctx)
|
||||
userID := ctxUserID(ctx)
|
||||
role := ctxUserRole(ctx)
|
||||
tenantID := middleware.GetTenantID(ctx)
|
||||
userID := middleware.GetUserID(ctx)
|
||||
role := middleware.GetUserRole(ctx)
|
||||
|
||||
page, pageSize := common.NormalizePagination(req.Page, req.PageSize)
|
||||
|
||||
@@ -69,9 +70,9 @@ func (s *sTenant) Notifications(ctx context.Context, req *v1.TenantNotifications
|
||||
|
||||
// UnreadCount 获取未读消息数量
|
||||
func (s *sTenant) UnreadCount(ctx context.Context, req *v1.TenantUnreadCountReq) (*v1.TenantUnreadCountRes, error) {
|
||||
tenantID := ctxTenantID(ctx)
|
||||
userID := ctxUserID(ctx)
|
||||
role := ctxUserRole(ctx)
|
||||
tenantID := middleware.GetTenantID(ctx)
|
||||
userID := middleware.GetUserID(ctx)
|
||||
role := middleware.GetUserRole(ctx)
|
||||
|
||||
// Count personal unread messages
|
||||
personalUnread, err := dao.NtfMessages.Ctx(ctx).
|
||||
@@ -108,8 +109,8 @@ func (s *sTenant) UnreadCount(ctx context.Context, req *v1.TenantUnreadCountReq)
|
||||
|
||||
// MarkRead 标记消息为已读
|
||||
func (s *sTenant) MarkRead(ctx context.Context, req *v1.TenantMarkReadReq) (*v1.TenantMarkReadRes, error) {
|
||||
tenantID := ctxTenantID(ctx)
|
||||
userID := ctxUserID(ctx)
|
||||
tenantID := middleware.GetTenantID(ctx)
|
||||
userID := middleware.GetUserID(ctx)
|
||||
|
||||
// Verify the message belongs to this tenant
|
||||
var msg *struct {
|
||||
@@ -132,7 +133,7 @@ func (s *sTenant) MarkRead(ctx context.Context, req *v1.TenantMarkReadReq) (*v1.
|
||||
|
||||
// Check role access for broadcast messages
|
||||
if msg.IsBroadcast == 1 && msg.TargetRoles != "" {
|
||||
role := ctxUserRole(ctx)
|
||||
role := middleware.GetUserRole(ctx)
|
||||
if !containsRole(msg.TargetRoles, role) {
|
||||
return nil, common.NewForbiddenError("无权访问该消息")
|
||||
}
|
||||
@@ -174,9 +175,9 @@ func (s *sTenant) MarkRead(ctx context.Context, req *v1.TenantMarkReadReq) (*v1.
|
||||
|
||||
// MarkAllRead 标记所有未读消息为已读
|
||||
func (s *sTenant) MarkAllRead(ctx context.Context, req *v1.TenantMarkAllReadReq) (*v1.TenantMarkAllReadRes, error) {
|
||||
tenantID := ctxTenantID(ctx)
|
||||
userID := ctxUserID(ctx)
|
||||
role := ctxUserRole(ctx)
|
||||
tenantID := middleware.GetTenantID(ctx)
|
||||
userID := middleware.GetUserID(ctx)
|
||||
role := middleware.GetUserRole(ctx)
|
||||
|
||||
// Mark all personal unread messages as read
|
||||
_, err := dao.NtfMessages.Ctx(ctx).
|
||||
@@ -233,8 +234,8 @@ func (s *sTenant) MarkAllRead(ctx context.Context, req *v1.TenantMarkAllReadReq)
|
||||
|
||||
// DeleteNotification 删除已读的个人消息(广播消息不允许用户删除)
|
||||
func (s *sTenant) DeleteNotification(ctx context.Context, req *v1.TenantNotificationDeleteReq) (*v1.TenantNotificationDeleteRes, error) {
|
||||
tenantID := ctxTenantID(ctx)
|
||||
userID := ctxUserID(ctx)
|
||||
tenantID := middleware.GetTenantID(ctx)
|
||||
userID := middleware.GetUserID(ctx)
|
||||
|
||||
var msg struct {
|
||||
ID int64 `json:"id"`
|
||||
@@ -275,8 +276,8 @@ func (s *sTenant) DeleteNotification(ctx context.Context, req *v1.TenantNotifica
|
||||
|
||||
// NotificationPreferencesGet 获取合并后的通知偏好(组织 + 用户)
|
||||
func (s *sTenant) NotificationPreferencesGet(ctx context.Context, req *v1.TenantNotificationPreferencesGetReq) (*v1.TenantNotificationPreferencesGetRes, error) {
|
||||
tenantID := ctxTenantID(ctx)
|
||||
userID := ctxUserID(ctx)
|
||||
tenantID := middleware.GetTenantID(ctx)
|
||||
userID := middleware.GetUserID(ctx)
|
||||
|
||||
orgPrefs := loadPreferences(ctx, tenantID, 0, "org")
|
||||
userPrefs := loadPreferences(ctx, tenantID, userID, "user")
|
||||
@@ -291,15 +292,15 @@ func (s *sTenant) NotificationPreferencesGet(ctx context.Context, req *v1.Tenant
|
||||
|
||||
// NotificationPreferencesUpdate 更新通知偏好
|
||||
func (s *sTenant) NotificationPreferencesUpdate(ctx context.Context, req *v1.TenantNotificationPreferencesUpdateReq) (*v1.TenantNotificationPreferencesUpdateRes, error) {
|
||||
tenantID := ctxTenantID(ctx)
|
||||
userID := ctxUserID(ctx)
|
||||
tenantID := middleware.GetTenantID(ctx)
|
||||
userID := middleware.GetUserID(ctx)
|
||||
|
||||
if req.Scope != "user" && req.Scope != "org" {
|
||||
return nil, common.NewBadRequestError("scope 必须是 'user' 或 'org'")
|
||||
}
|
||||
|
||||
// Safety categories cannot be fully disabled
|
||||
if req.Scope == "org" && ctxUserRole(ctx) != "owner" {
|
||||
if req.Scope == "org" && middleware.GetUserRole(ctx) != "owner" {
|
||||
return nil, common.NewForbiddenError("仅组织所有者可修改组织级通知偏好")
|
||||
}
|
||||
validateSafetyCategories(req.Preferences)
|
||||
@@ -524,15 +525,9 @@ func splitString(s, sep string) []string {
|
||||
|
||||
// ExportNotifications exports the tenant notification list as CSV or Excel.
|
||||
func (s *sTenant) ExportNotifications(ctx context.Context, req *v1.TenantNotificationsExportReq) (*v1.TenantNotificationsExportRes, error) {
|
||||
r := g.RequestFromCtx(ctx)
|
||||
format := req.Format
|
||||
if format == "" {
|
||||
format = "csv"
|
||||
}
|
||||
|
||||
tenantID := ctxTenantID(ctx)
|
||||
userID := ctxUserID(ctx)
|
||||
role := ctxUserRole(ctx)
|
||||
tenantID := middleware.GetTenantID(ctx)
|
||||
userID := middleware.GetUserID(ctx)
|
||||
role := middleware.GetUserRole(ctx)
|
||||
|
||||
columns := []export.Column{
|
||||
{Field: "id", Header: "ID"},
|
||||
@@ -544,49 +539,12 @@ func (s *sTenant) ExportNotifications(ctx context.Context, req *v1.TenantNotific
|
||||
}
|
||||
|
||||
config := export.Config{
|
||||
Format: format,
|
||||
Format: req.Format,
|
||||
Filename: "通知列表_" + gtime.Now().Format("Ymd_His"),
|
||||
Columns: columns,
|
||||
}
|
||||
|
||||
if format == "xlsx" {
|
||||
items := make([]*v1.TenantNotificationItem, 0)
|
||||
err := dao.NtfMessages.Ctx(ctx).
|
||||
Where("tenant_id", tenantID).
|
||||
Where("(user_id = ? OR (user_id IS NULL AND is_broadcast = 1 AND (target_roles IS NULL OR ? = ANY(string_to_array(target_roles, ',')))))", userID, role).
|
||||
OrderDesc("created_at").
|
||||
Scan(&items)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
data := make([]map[string]any, 0, len(items))
|
||||
for _, item := range items {
|
||||
isRead := 0
|
||||
if item.IsBroadcast == 1 {
|
||||
readCount, _ := dao.NtfReadStatus.Ctx(ctx).
|
||||
Where("message_id", item.Id).
|
||||
Where("user_id", userID).
|
||||
Count()
|
||||
if readCount > 0 {
|
||||
isRead = 1
|
||||
}
|
||||
} else {
|
||||
isRead = item.IsRead
|
||||
}
|
||||
data = append(data, map[string]any{
|
||||
"id": item.Id,
|
||||
"type": item.Type,
|
||||
"title": item.Title,
|
||||
"channel": item.Channel,
|
||||
"is_read": isRead,
|
||||
"created_at": item.CreatedAt,
|
||||
})
|
||||
}
|
||||
return nil, export.WriteExcel(r, config, data)
|
||||
}
|
||||
|
||||
return nil, export.StreamCSV(r, config, func(yield func(map[string]any) bool) {
|
||||
return nil, export.GenericExport(ctx, config, func(yield func(map[string]any) bool) {
|
||||
offset := 0
|
||||
for {
|
||||
items := make([]*v1.TenantNotificationItem, 0)
|
||||
|
||||
@@ -13,6 +13,7 @@ import (
|
||||
|
||||
v1 "github.com/qianfree/team-api/api/tenant/v1"
|
||||
"github.com/qianfree/team-api/internal/logic/common"
|
||||
"github.com/qianfree/team-api/internal/middleware"
|
||||
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
)
|
||||
@@ -278,8 +279,8 @@ func (s *sTenant) LinkOAuth(ctx context.Context, req *v1.OAuthLinkReq) (*v1.OAut
|
||||
return nil, common.NewBusinessError(10059, "该 OAuth 供应商未启用")
|
||||
}
|
||||
|
||||
tenantID := ctxTenantID(ctx)
|
||||
userID := ctxUserID(ctx)
|
||||
tenantID := middleware.GetTenantID(ctx)
|
||||
userID := middleware.GetUserID(ctx)
|
||||
|
||||
oauthToken, err := provider.ExchangeToken(ctx, req.Code)
|
||||
if err != nil {
|
||||
@@ -336,8 +337,8 @@ func (s *sTenant) LinkOAuth(ctx context.Context, req *v1.OAuthLinkReq) (*v1.OAut
|
||||
|
||||
// UnlinkOAuth 解绑 OAuth 账号
|
||||
func (s *sTenant) UnlinkOAuth(ctx context.Context, req *v1.OAuthUnlinkReq) (*v1.OAuthUnlinkRes, error) {
|
||||
tenantID := ctxTenantID(ctx)
|
||||
userID := ctxUserID(ctx)
|
||||
tenantID := middleware.GetTenantID(ctx)
|
||||
userID := middleware.GetUserID(ctx)
|
||||
|
||||
_, err := g.DB().Model("tnt_oauth_identities").Ctx(ctx).
|
||||
Where("tenant_id", tenantID).
|
||||
@@ -353,8 +354,8 @@ func (s *sTenant) UnlinkOAuth(ctx context.Context, req *v1.OAuthUnlinkReq) (*v1.
|
||||
|
||||
// ListOAuthProviders 获取已绑定的 OAuth 供应商列表
|
||||
func (s *sTenant) ListOAuthProviders(ctx context.Context, req *v1.OAuthListProvidersReq) (*v1.OAuthListProvidersRes, error) {
|
||||
tenantID := ctxTenantID(ctx)
|
||||
userID := ctxUserID(ctx)
|
||||
tenantID := middleware.GetTenantID(ctx)
|
||||
userID := middleware.GetUserID(ctx)
|
||||
|
||||
type row struct {
|
||||
Provider string `json:"provider" orm:"provider"`
|
||||
|
||||
@@ -15,6 +15,7 @@ import (
|
||||
v1 "github.com/qianfree/team-api/api/tenant/v1"
|
||||
"github.com/qianfree/team-api/internal/dao"
|
||||
"github.com/qianfree/team-api/internal/logic/common"
|
||||
"github.com/qianfree/team-api/internal/middleware"
|
||||
do "github.com/qianfree/team-api/internal/model/do"
|
||||
"github.com/qianfree/team-api/internal/model/entity"
|
||||
"github.com/qianfree/team-api/internal/utility/crypto"
|
||||
@@ -25,11 +26,11 @@ import (
|
||||
// ============================================================
|
||||
|
||||
func (s *sTenant) OpenAppList(ctx context.Context, req *v1.OpenAppListReq) (*v1.OpenAppListRes, error) {
|
||||
role := ctxUserRole(ctx)
|
||||
role := middleware.GetUserRole(ctx)
|
||||
if role != "owner" && role != "admin" {
|
||||
return nil, common.NewForbiddenError("需要 owner 或 admin 权限")
|
||||
}
|
||||
tenantID := ctxTenantID(ctx)
|
||||
tenantID := middleware.GetTenantID(ctx)
|
||||
page := req.Page
|
||||
pageSize := req.PageSize
|
||||
if page < 1 {
|
||||
@@ -81,11 +82,11 @@ func (s *sTenant) OpenAppList(ctx context.Context, req *v1.OpenAppListReq) (*v1.
|
||||
}
|
||||
|
||||
func (s *sTenant) OpenAppCreate(ctx context.Context, req *v1.OpenAppCreateReq) (*v1.OpenAppCreateRes, error) {
|
||||
role := ctxUserRole(ctx)
|
||||
role := middleware.GetUserRole(ctx)
|
||||
if role != "owner" && role != "admin" {
|
||||
return nil, common.NewForbiddenError("需要 owner 或 admin 权限")
|
||||
}
|
||||
tenantID := ctxTenantID(ctx)
|
||||
tenantID := middleware.GetTenantID(ctx)
|
||||
|
||||
// Generate app_id (opn_xxxxxxxx)
|
||||
appIDBytes := make([]byte, 12)
|
||||
@@ -152,11 +153,11 @@ func (s *sTenant) OpenAppCreate(ctx context.Context, req *v1.OpenAppCreateReq) (
|
||||
}
|
||||
|
||||
func (s *sTenant) OpenAppUpdate(ctx context.Context, req *v1.OpenAppUpdateReq) (*v1.OpenAppUpdateRes, error) {
|
||||
role := ctxUserRole(ctx)
|
||||
role := middleware.GetUserRole(ctx)
|
||||
if role != "owner" && role != "admin" {
|
||||
return nil, common.NewForbiddenError("需要 owner 或 admin 权限")
|
||||
}
|
||||
tenantID := ctxTenantID(ctx)
|
||||
tenantID := middleware.GetTenantID(ctx)
|
||||
data := g.Map{}
|
||||
|
||||
if req.Name != nil {
|
||||
@@ -186,11 +187,11 @@ func (s *sTenant) OpenAppUpdate(ctx context.Context, req *v1.OpenAppUpdateReq) (
|
||||
}
|
||||
|
||||
func (s *sTenant) OpenAppDelete(ctx context.Context, req *v1.OpenAppDeleteReq) (*v1.OpenAppDeleteRes, error) {
|
||||
role := ctxUserRole(ctx)
|
||||
role := middleware.GetUserRole(ctx)
|
||||
if role != "owner" && role != "admin" {
|
||||
return nil, common.NewForbiddenError("需要 owner 或 admin 权限")
|
||||
}
|
||||
tenantID := ctxTenantID(ctx)
|
||||
tenantID := middleware.GetTenantID(ctx)
|
||||
_, err := dao.OpnApps.Ctx(ctx).Where("id", req.Id).Where("tenant_id", tenantID).Delete()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -201,11 +202,11 @@ func (s *sTenant) OpenAppDelete(ctx context.Context, req *v1.OpenAppDeleteReq) (
|
||||
}
|
||||
|
||||
func (s *sTenant) OpenAppResetSecret(ctx context.Context, req *v1.OpenAppResetSecretReq) (*v1.OpenAppResetSecretRes, error) {
|
||||
role := ctxUserRole(ctx)
|
||||
role := middleware.GetUserRole(ctx)
|
||||
if role != "owner" && role != "admin" {
|
||||
return nil, common.NewForbiddenError("需要 owner 或 admin 权限")
|
||||
}
|
||||
tenantID := ctxTenantID(ctx)
|
||||
tenantID := middleware.GetTenantID(ctx)
|
||||
|
||||
// Generate new secret
|
||||
secretBytes := make([]byte, 24)
|
||||
@@ -241,11 +242,11 @@ func (s *sTenant) OpenAppResetSecret(ctx context.Context, req *v1.OpenAppResetSe
|
||||
}
|
||||
|
||||
func (s *sTenant) OpenAppToggleStatus(ctx context.Context, req *v1.OpenAppToggleStatusReq) (*v1.OpenAppToggleStatusRes, error) {
|
||||
role := ctxUserRole(ctx)
|
||||
role := middleware.GetUserRole(ctx)
|
||||
if role != "owner" && role != "admin" {
|
||||
return nil, common.NewForbiddenError("需要 owner 或 admin 权限")
|
||||
}
|
||||
tenantID := ctxTenantID(ctx)
|
||||
tenantID := middleware.GetTenantID(ctx)
|
||||
_, err := dao.OpnApps.Ctx(ctx).Where("id", req.Id).Where("tenant_id", tenantID).Data(g.Map{
|
||||
"status": req.Status,
|
||||
}).Update()
|
||||
@@ -257,11 +258,11 @@ func (s *sTenant) OpenAppToggleStatus(ctx context.Context, req *v1.OpenAppToggle
|
||||
// ============================================================
|
||||
|
||||
func (s *sTenant) WebhookConfigList(ctx context.Context, _ *v1.WebhookConfigListReq) (*v1.WebhookConfigListRes, error) {
|
||||
role := ctxUserRole(ctx)
|
||||
role := middleware.GetUserRole(ctx)
|
||||
if role != "owner" && role != "admin" {
|
||||
return nil, common.NewForbiddenError("需要 owner 或 admin 权限")
|
||||
}
|
||||
tenantID := ctxTenantID(ctx)
|
||||
tenantID := middleware.GetTenantID(ctx)
|
||||
var configs []entity.OpnWebhookConfigs
|
||||
err := dao.OpnWebhookConfigs.Ctx(ctx).Where("tenant_id", tenantID).OrderDesc("created_at").Scan(&configs)
|
||||
if err != nil {
|
||||
@@ -293,11 +294,11 @@ func (s *sTenant) WebhookConfigList(ctx context.Context, _ *v1.WebhookConfigList
|
||||
}
|
||||
|
||||
func (s *sTenant) WebhookConfigCreate(ctx context.Context, req *v1.WebhookConfigCreateReq) (*v1.WebhookConfigCreateRes, error) {
|
||||
role := ctxUserRole(ctx)
|
||||
role := middleware.GetUserRole(ctx)
|
||||
if role != "owner" && role != "admin" {
|
||||
return nil, common.NewForbiddenError("需要 owner 或 admin 权限")
|
||||
}
|
||||
tenantID := ctxTenantID(ctx)
|
||||
tenantID := middleware.GetTenantID(ctx)
|
||||
|
||||
// Generate signing key
|
||||
keyBytes := make([]byte, 24)
|
||||
@@ -330,11 +331,11 @@ func (s *sTenant) WebhookConfigCreate(ctx context.Context, req *v1.WebhookConfig
|
||||
}
|
||||
|
||||
func (s *sTenant) WebhookConfigUpdate(ctx context.Context, req *v1.WebhookConfigUpdateReq) (*v1.WebhookConfigUpdateRes, error) {
|
||||
role := ctxUserRole(ctx)
|
||||
role := middleware.GetUserRole(ctx)
|
||||
if role != "owner" && role != "admin" {
|
||||
return nil, common.NewForbiddenError("需要 owner 或 admin 权限")
|
||||
}
|
||||
tenantID := ctxTenantID(ctx)
|
||||
tenantID := middleware.GetTenantID(ctx)
|
||||
data := g.Map{}
|
||||
|
||||
if req.Name != nil {
|
||||
@@ -363,21 +364,21 @@ func (s *sTenant) WebhookConfigUpdate(ctx context.Context, req *v1.WebhookConfig
|
||||
}
|
||||
|
||||
func (s *sTenant) WebhookConfigDelete(ctx context.Context, req *v1.WebhookConfigDeleteReq) (*v1.WebhookConfigDeleteRes, error) {
|
||||
role := ctxUserRole(ctx)
|
||||
role := middleware.GetUserRole(ctx)
|
||||
if role != "owner" && role != "admin" {
|
||||
return nil, common.NewForbiddenError("需要 owner 或 admin 权限")
|
||||
}
|
||||
tenantID := ctxTenantID(ctx)
|
||||
tenantID := middleware.GetTenantID(ctx)
|
||||
_, err := dao.OpnWebhookConfigs.Ctx(ctx).Where("id", req.Id).Where("tenant_id", tenantID).Delete()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
func (s *sTenant) WebhookDeliveryLogs(ctx context.Context, req *v1.WebhookDeliveryLogsReq) (*v1.WebhookDeliveryLogsRes, error) {
|
||||
role := ctxUserRole(ctx)
|
||||
role := middleware.GetUserRole(ctx)
|
||||
if role != "owner" && role != "admin" {
|
||||
return nil, common.NewForbiddenError("需要 owner 或 admin 权限")
|
||||
}
|
||||
tenantID := ctxTenantID(ctx)
|
||||
tenantID := middleware.GetTenantID(ctx)
|
||||
page := req.Page
|
||||
pageSize := req.PageSize
|
||||
if page < 1 {
|
||||
@@ -423,11 +424,11 @@ func (s *sTenant) WebhookDeliveryLogs(ctx context.Context, req *v1.WebhookDelive
|
||||
}
|
||||
|
||||
func (s *sTenant) WebhookRetry(ctx context.Context, req *v1.WebhookRetryReq) (*v1.WebhookRetryRes, error) {
|
||||
role := ctxUserRole(ctx)
|
||||
role := middleware.GetUserRole(ctx)
|
||||
if role != "owner" && role != "admin" {
|
||||
return nil, common.NewForbiddenError("需要 owner 或 admin 权限")
|
||||
}
|
||||
tenantID := ctxTenantID(ctx)
|
||||
tenantID := middleware.GetTenantID(ctx)
|
||||
|
||||
result, err := dao.OpnWebhookEvents.Ctx(ctx).
|
||||
Where("id", req.EventId).
|
||||
|
||||
@@ -7,7 +7,6 @@ import (
|
||||
|
||||
v1 "github.com/qianfree/team-api/api/tenant/v1"
|
||||
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/os/gtime"
|
||||
|
||||
"github.com/qianfree/team-api/internal/dao"
|
||||
@@ -15,16 +14,17 @@ import (
|
||||
|
||||
lcommon "github.com/qianfree/team-api/internal/logic/common"
|
||||
"github.com/qianfree/team-api/internal/logic/payment"
|
||||
"github.com/qianfree/team-api/internal/middleware"
|
||||
"github.com/qianfree/team-api/internal/utility/export"
|
||||
)
|
||||
|
||||
// OrderList 获取租户订单列表
|
||||
func (s *sTenant) OrderList(ctx context.Context, req *v1.TenantOrderListReq) (*v1.TenantOrderListRes, error) {
|
||||
role := ctxUserRole(ctx)
|
||||
role := middleware.GetUserRole(ctx)
|
||||
if role != "owner" && role != "admin" {
|
||||
return nil, lcommon.NewForbiddenError("需要 owner 或 admin 权限")
|
||||
}
|
||||
tenantID := ctxTenantID(ctx)
|
||||
tenantID := middleware.GetTenantID(ctx)
|
||||
page, pageSize := lcommon.NormalizePagination(req.Page, req.PageSize)
|
||||
status := req.Status
|
||||
|
||||
@@ -52,11 +52,11 @@ func (s *sTenant) OrderList(ctx context.Context, req *v1.TenantOrderListReq) (*v
|
||||
|
||||
// OrderDetail 获取订单详情
|
||||
func (s *sTenant) OrderDetail(ctx context.Context, req *v1.TenantOrderDetailReq) (*v1.TenantOrderDetailRes, error) {
|
||||
role := ctxUserRole(ctx)
|
||||
role := middleware.GetUserRole(ctx)
|
||||
if role != "owner" && role != "admin" {
|
||||
return nil, lcommon.NewForbiddenError("需要 owner 或 admin 权限")
|
||||
}
|
||||
tenantID := ctxTenantID(ctx)
|
||||
tenantID := middleware.GetTenantID(ctx)
|
||||
var order map[string]any
|
||||
err := dao.OrdOrders.Ctx(ctx).
|
||||
Where("id", req.Id).
|
||||
@@ -73,12 +73,12 @@ func (s *sTenant) OrderDetail(ctx context.Context, req *v1.TenantOrderDetailReq)
|
||||
|
||||
// OrderCreate 创建订单
|
||||
func (s *sTenant) OrderCreate(ctx context.Context, req *v1.TenantOrderCreateReq) (*v1.TenantOrderCreateRes, error) {
|
||||
role := ctxUserRole(ctx)
|
||||
role := middleware.GetUserRole(ctx)
|
||||
if role != "owner" && role != "admin" {
|
||||
return nil, lcommon.NewForbiddenError("需要 owner 或 admin 权限")
|
||||
}
|
||||
tenantID := ctxTenantID(ctx)
|
||||
userID := ctxUserID(ctx)
|
||||
tenantID := middleware.GetTenantID(ctx)
|
||||
userID := middleware.GetUserID(ctx)
|
||||
planID := req.PlanID
|
||||
months := req.Months
|
||||
|
||||
@@ -141,11 +141,11 @@ func (s *sTenant) OrderCreate(ctx context.Context, req *v1.TenantOrderCreateReq)
|
||||
|
||||
// OrderCancel 取消订单
|
||||
func (s *sTenant) OrderCancel(ctx context.Context, req *v1.TenantOrderCancelReq) (*v1.TenantOrderCancelRes, error) {
|
||||
role := ctxUserRole(ctx)
|
||||
role := middleware.GetUserRole(ctx)
|
||||
if role != "owner" && role != "admin" {
|
||||
return nil, lcommon.NewForbiddenError("需要 owner 或 admin 权限")
|
||||
}
|
||||
tenantID := ctxTenantID(ctx)
|
||||
tenantID := middleware.GetTenantID(ctx)
|
||||
result, err := dao.OrdOrders.Ctx(ctx).
|
||||
Where("id", req.Id).
|
||||
Where("tenant_id", tenantID).
|
||||
@@ -166,11 +166,11 @@ func (s *sTenant) OrderCancel(ctx context.Context, req *v1.TenantOrderCancelReq)
|
||||
|
||||
// OrderPay 支付订单
|
||||
func (s *sTenant) OrderPay(ctx context.Context, req *v1.TenantOrderPayReq) (*v1.TenantOrderPayRes, error) {
|
||||
role := ctxUserRole(ctx)
|
||||
role := middleware.GetUserRole(ctx)
|
||||
if role != "owner" && role != "admin" {
|
||||
return nil, lcommon.NewForbiddenError("需要 owner 或 admin 权限")
|
||||
}
|
||||
tenantID := ctxTenantID(ctx)
|
||||
tenantID := middleware.GetTenantID(ctx)
|
||||
orderID := req.Id
|
||||
|
||||
// 支付渠道
|
||||
@@ -231,7 +231,7 @@ func (s *sTenant) OrderPay(ctx context.Context, req *v1.TenantOrderPayReq) (*v1.
|
||||
|
||||
// PaymentInfo 获取租户可用的支付信息(渠道列表、金额选项、折扣)
|
||||
func (s *sTenant) PaymentInfo(ctx context.Context, req *v1.TenantPaymentInfoReq) (*v1.TenantPaymentInfoRes, error) {
|
||||
role := ctxUserRole(ctx)
|
||||
role := middleware.GetUserRole(ctx)
|
||||
if role != "owner" && role != "admin" {
|
||||
return nil, lcommon.NewForbiddenError("需要 owner 或 admin 权限")
|
||||
}
|
||||
@@ -294,12 +294,12 @@ func updateOrderPaymentChannel(ctx context.Context, orderID int64, channel, paym
|
||||
|
||||
// RechargeCreate 创建充值订单并发起支付(一步完成)
|
||||
func (s *sTenant) RechargeCreate(ctx context.Context, req *v1.TenantRechargeCreateReq) (*v1.TenantRechargeCreateRes, error) {
|
||||
role := ctxUserRole(ctx)
|
||||
role := middleware.GetUserRole(ctx)
|
||||
if role != "owner" && role != "admin" {
|
||||
return nil, lcommon.NewForbiddenError("需要 owner 或 admin 权限")
|
||||
}
|
||||
tenantID := ctxTenantID(ctx)
|
||||
userID := ctxUserID(ctx)
|
||||
tenantID := middleware.GetTenantID(ctx)
|
||||
userID := middleware.GetUserID(ctx)
|
||||
|
||||
// 1. 校验金额
|
||||
settings, _ := payment.GetGlobalPaymentSettings(ctx)
|
||||
@@ -393,18 +393,12 @@ func (s *sTenant) RechargeCreate(ctx context.Context, req *v1.TenantRechargeCrea
|
||||
|
||||
// ExportOrders exports the tenant order list as CSV or Excel.
|
||||
func (s *sTenant) ExportOrders(ctx context.Context, req *v1.TenantOrderExportReq) (*v1.TenantOrderExportRes, error) {
|
||||
role := ctxUserRole(ctx)
|
||||
role := middleware.GetUserRole(ctx)
|
||||
if role != "owner" && role != "admin" {
|
||||
return nil, lcommon.NewForbiddenError("需要 owner 或 admin 权限")
|
||||
}
|
||||
|
||||
r := g.RequestFromCtx(ctx)
|
||||
format := req.Format
|
||||
if format == "" {
|
||||
format = "csv"
|
||||
}
|
||||
|
||||
tenantID := ctxTenantID(ctx)
|
||||
tenantID := middleware.GetTenantID(ctx)
|
||||
|
||||
columns := []export.Column{
|
||||
{Field: "id", Header: "ID"},
|
||||
@@ -418,39 +412,12 @@ func (s *sTenant) ExportOrders(ctx context.Context, req *v1.TenantOrderExportReq
|
||||
}
|
||||
|
||||
config := export.Config{
|
||||
Format: format,
|
||||
Format: req.Format,
|
||||
Filename: "订单列表_" + gtime.Now().Format("Ymd_His"),
|
||||
Columns: columns,
|
||||
}
|
||||
|
||||
if format == "xlsx" {
|
||||
orders := make([]*v1.TenantOrderItem, 0)
|
||||
query := dao.OrdOrders.Ctx(ctx).Where("tenant_id", tenantID)
|
||||
if req.Status != "" {
|
||||
query = query.Where("status", req.Status)
|
||||
}
|
||||
err := query.OrderDesc("created_at").Scan(&orders)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
data := make([]map[string]any, 0, len(orders))
|
||||
for _, o := range orders {
|
||||
data = append(data, map[string]any{
|
||||
"id": o.Id,
|
||||
"order_no": o.OrderNo,
|
||||
"order_type": o.OrderType,
|
||||
"amount": o.Amount,
|
||||
"final_amount": o.FinalAmount,
|
||||
"payment_channel": o.PaymentChannel,
|
||||
"status": o.Status,
|
||||
"created_at": o.CreatedAt,
|
||||
})
|
||||
}
|
||||
return nil, export.WriteExcel(r, config, data)
|
||||
}
|
||||
|
||||
return nil, export.StreamCSV(r, config, func(yield func(map[string]any) bool) {
|
||||
return nil, export.GenericExport(ctx, config, func(yield func(map[string]any) bool) {
|
||||
offset := 0
|
||||
for {
|
||||
orders := make([]*v1.TenantOrderItem, 0)
|
||||
|
||||
@@ -9,12 +9,13 @@ import (
|
||||
v1 "github.com/qianfree/team-api/api/tenant/v1"
|
||||
"github.com/qianfree/team-api/internal/dao"
|
||||
"github.com/qianfree/team-api/internal/logic/common"
|
||||
"github.com/qianfree/team-api/internal/middleware"
|
||||
"github.com/qianfree/team-api/internal/utility/crypto"
|
||||
)
|
||||
|
||||
// ownerOnly checks if the current user is the tenant owner.
|
||||
func ownerOnly(ctx context.Context) error {
|
||||
if ctxUserRole(ctx) != "owner" {
|
||||
if middleware.GetUserRole(ctx) != "owner" {
|
||||
return common.NewForbiddenError("仅组织所有者可执行此操作")
|
||||
}
|
||||
return nil
|
||||
@@ -22,7 +23,7 @@ func ownerOnly(ctx context.Context) error {
|
||||
|
||||
// GetOrgInfo returns tenant organization info.
|
||||
func (s *sTenant) GetOrgInfo(ctx context.Context, req *v1.TenantOrgInfoReq) (*v1.TenantOrgInfoRes, error) {
|
||||
tenantID := ctxTenantID(ctx)
|
||||
tenantID := middleware.GetTenantID(ctx)
|
||||
|
||||
var tenant *struct {
|
||||
Id int64 `json:"id"`
|
||||
@@ -79,7 +80,7 @@ func (s *sTenant) UpdateOrgInfo(ctx context.Context, req *v1.TenantOrgUpdateReq)
|
||||
if err := ownerOnly(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tenantID := ctxTenantID(ctx)
|
||||
tenantID := middleware.GetTenantID(ctx)
|
||||
|
||||
data := do.TntTenants{}
|
||||
if req.Name != nil {
|
||||
@@ -101,8 +102,8 @@ func (s *sTenant) TransferOwnership(ctx context.Context, req *v1.TenantOrgTransf
|
||||
if err := ownerOnly(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tenantID := ctxTenantID(ctx)
|
||||
currentOwnerID := ctxUserID(ctx)
|
||||
tenantID := middleware.GetTenantID(ctx)
|
||||
currentOwnerID := middleware.GetUserID(ctx)
|
||||
|
||||
if req.NewOwnerID == currentOwnerID {
|
||||
return nil, common.NewBadRequestError("不能转让给自己")
|
||||
@@ -183,7 +184,7 @@ func (s *sTenant) TransferOwnership(ctx context.Context, req *v1.TenantOrgTransf
|
||||
|
||||
// GetProfile returns current user's profile.
|
||||
func (s *sTenant) GetProfile(ctx context.Context, req *v1.TenantProfileReq) (*v1.TenantProfileRes, error) {
|
||||
userID := ctxUserID(ctx)
|
||||
userID := middleware.GetUserID(ctx)
|
||||
|
||||
var user *struct {
|
||||
Id int64 `json:"id"`
|
||||
@@ -216,7 +217,7 @@ func (s *sTenant) GetProfile(ctx context.Context, req *v1.TenantProfileReq) (*v1
|
||||
|
||||
// UpdateProfile updates current user's profile.
|
||||
func (s *sTenant) UpdateProfile(ctx context.Context, req *v1.TenantProfileUpdateReq) (*v1.TenantProfileUpdateRes, error) {
|
||||
userID := ctxUserID(ctx)
|
||||
userID := middleware.GetUserID(ctx)
|
||||
|
||||
data := do.TntUsers{}
|
||||
if req.DisplayName != nil {
|
||||
|
||||
@@ -8,12 +8,13 @@ import (
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
|
||||
v1 "github.com/qianfree/team-api/api/tenant/v1"
|
||||
"github.com/qianfree/team-api/internal/middleware"
|
||||
)
|
||||
|
||||
// PersonalDashboard returns the personal dashboard overview for the current user.
|
||||
func (s *sTenant) PersonalDashboard(ctx context.Context, req *v1.PersonalDashboardReq) (*v1.PersonalDashboardRes, error) {
|
||||
userID := ctxUserID(ctx)
|
||||
tenantID := ctxTenantID(ctx)
|
||||
userID := middleware.GetUserID(ctx)
|
||||
tenantID := middleware.GetTenantID(ctx)
|
||||
|
||||
now := time.Now()
|
||||
todayStart := now.Format("2006-01-02") + " 00:00:00"
|
||||
@@ -206,8 +207,8 @@ func (s *sTenant) PersonalDashboard(ctx context.Context, req *v1.PersonalDashboa
|
||||
|
||||
// PersonalTokenTrends returns daily token usage trends for the current user.
|
||||
func (s *sTenant) PersonalTokenTrends(ctx context.Context, req *v1.PersonalTokenTrendsReq) (*v1.PersonalTokenTrendsRes, error) {
|
||||
userID := ctxUserID(ctx)
|
||||
tenantID := ctxTenantID(ctx)
|
||||
userID := middleware.GetUserID(ctx)
|
||||
tenantID := middleware.GetTenantID(ctx)
|
||||
days := req.Days
|
||||
if days <= 0 || days > 90 {
|
||||
days = 30
|
||||
@@ -240,8 +241,8 @@ func (s *sTenant) PersonalTokenTrends(ctx context.Context, req *v1.PersonalToken
|
||||
|
||||
// PersonalModelDistribution returns model usage distribution for the current user.
|
||||
func (s *sTenant) PersonalModelDistribution(ctx context.Context, req *v1.PersonalModelDistReq) (*v1.PersonalModelDistRes, error) {
|
||||
userID := ctxUserID(ctx)
|
||||
tenantID := ctxTenantID(ctx)
|
||||
userID := middleware.GetUserID(ctx)
|
||||
tenantID := middleware.GetTenantID(ctx)
|
||||
days := req.Days
|
||||
if days <= 0 || days > 90 {
|
||||
days = 30
|
||||
@@ -275,8 +276,8 @@ func (s *sTenant) PersonalModelDistribution(ctx context.Context, req *v1.Persona
|
||||
|
||||
// PersonalApiKeyUsage returns per-API-key usage breakdown for the current user.
|
||||
func (s *sTenant) PersonalApiKeyUsage(ctx context.Context, req *v1.PersonalApiKeyUsageReq) (*v1.PersonalApiKeyUsageRes, error) {
|
||||
userID := ctxUserID(ctx)
|
||||
tenantID := ctxTenantID(ctx)
|
||||
userID := middleware.GetUserID(ctx)
|
||||
tenantID := middleware.GetTenantID(ctx)
|
||||
days := req.Days
|
||||
if days <= 0 || days > 90 {
|
||||
days = 30
|
||||
|
||||
@@ -11,11 +11,12 @@ import (
|
||||
v1 "github.com/qianfree/team-api/api/tenant/v1"
|
||||
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/qianfree/team-api/internal/middleware"
|
||||
)
|
||||
|
||||
// PlanList 获取可购买的套餐列表(仅 active)
|
||||
func (s *sTenant) PlanList(ctx context.Context, req *v1.TenantPlanListReq) (*v1.TenantPlanListRes, error) {
|
||||
role := ctxUserRole(ctx)
|
||||
role := middleware.GetUserRole(ctx)
|
||||
if role != "owner" && role != "admin" {
|
||||
return nil, common.NewForbiddenError("需要 owner 或 admin 权限")
|
||||
}
|
||||
@@ -47,11 +48,11 @@ func (s *sTenant) PlanList(ctx context.Context, req *v1.TenantPlanListReq) (*v1.
|
||||
|
||||
// PlanCurrent 获取租户当前套餐
|
||||
func (s *sTenant) PlanCurrent(ctx context.Context, req *v1.TenantPlanCurrentReq) (*v1.TenantPlanCurrentRes, error) {
|
||||
role := ctxUserRole(ctx)
|
||||
role := middleware.GetUserRole(ctx)
|
||||
if role != "owner" && role != "admin" {
|
||||
return nil, common.NewForbiddenError("需要 owner 或 admin 权限")
|
||||
}
|
||||
tenantID := ctxTenantID(ctx)
|
||||
tenantID := middleware.GetTenantID(ctx)
|
||||
var plan v1.TenantPlanCurrentRes
|
||||
err := dao.PlnTenantPlans.Ctx(ctx).As("tp").
|
||||
Fields("tp.id, tp.tenant_id, tp.plan_id, tp.status, tp.start_at, tp.end_at, tp.auto_renew, tp.monthly_quota_tokens, tp.used_tokens, tp.last_reset_at, p.name, p.identifier, p.description, p.monthly_price, p.yearly_price").
|
||||
@@ -74,11 +75,11 @@ func (s *sTenant) PlanCurrent(ctx context.Context, req *v1.TenantPlanCurrentReq)
|
||||
|
||||
// PlanCancelAutoRenew 取消自动续费
|
||||
func (s *sTenant) PlanCancelAutoRenew(ctx context.Context, req *v1.TenantPlanCancelAutoRenewReq) (*v1.TenantPlanCancelAutoRenewRes, error) {
|
||||
role := ctxUserRole(ctx)
|
||||
role := middleware.GetUserRole(ctx)
|
||||
if role != "owner" && role != "admin" {
|
||||
return nil, common.NewForbiddenError("需要 owner 或 admin 权限")
|
||||
}
|
||||
tenantID := ctxTenantID(ctx)
|
||||
tenantID := middleware.GetTenantID(ctx)
|
||||
_, err := dao.PlnTenantPlans.Ctx(ctx).
|
||||
Where("tenant_id", tenantID).
|
||||
Where("status", "active").
|
||||
|
||||
@@ -17,6 +17,7 @@ import (
|
||||
"github.com/qianfree/team-api/internal/logic/billing"
|
||||
"github.com/qianfree/team-api/internal/logic/common"
|
||||
"github.com/qianfree/team-api/internal/logic/relay"
|
||||
"github.com/qianfree/team-api/internal/middleware"
|
||||
relaycommon "github.com/qianfree/team-api/relay/common"
|
||||
"github.com/qianfree/team-api/relay/constant"
|
||||
"github.com/qianfree/team-api/relay/handler"
|
||||
@@ -32,8 +33,8 @@ type playgroundApiKey struct {
|
||||
}
|
||||
|
||||
func findActiveApiKey(ctx context.Context) (*playgroundApiKey, error) {
|
||||
tenantID := ctxTenantID(ctx)
|
||||
userID := ctxUserID(ctx)
|
||||
tenantID := middleware.GetTenantID(ctx)
|
||||
userID := middleware.GetUserID(ctx)
|
||||
|
||||
var key *playgroundApiKey
|
||||
err := dao.ApiKeys.Ctx(ctx).
|
||||
@@ -57,8 +58,8 @@ func buildPlaygroundRelayContext(ctx context.Context, key *playgroundApiKey, rec
|
||||
requestID = v.(string)
|
||||
}
|
||||
return &handler.RelayContext{
|
||||
TenantID: ctxTenantID(ctx),
|
||||
UserID: ctxUserID(ctx),
|
||||
TenantID: middleware.GetTenantID(ctx),
|
||||
UserID: middleware.GetUserID(ctx),
|
||||
ApiKeyID: key.Id,
|
||||
RequestID: requestID,
|
||||
Writer: recorder,
|
||||
@@ -439,7 +440,7 @@ func (s *sTenant) PlaygroundRerank(ctx context.Context, req *v1.PlaygroundRerank
|
||||
// ============================================================
|
||||
|
||||
func (s *sTenant) SandboxChat(ctx context.Context, req *v1.SandboxChatReq) (*v1.SandboxChatRes, error) {
|
||||
tenantID := ctxTenantID(ctx)
|
||||
tenantID := middleware.GetTenantID(ctx)
|
||||
|
||||
now := time.Now()
|
||||
quotaKey := fmt.Sprintf("sandbox:quota:%d:%s", tenantID, now.Format("200601"))
|
||||
@@ -480,7 +481,7 @@ func (s *sTenant) SandboxChat(ctx context.Context, req *v1.SandboxChatReq) (*v1.
|
||||
}
|
||||
|
||||
func (s *sTenant) SandboxQuota(ctx context.Context, req *v1.SandboxQuotaReq) (*v1.SandboxQuotaRes, error) {
|
||||
tenantID := ctxTenantID(ctx)
|
||||
tenantID := middleware.GetTenantID(ctx)
|
||||
|
||||
defaultQuota := g.Cfg().MustGet(ctx, "sandbox.sandbox_default_quota").Int()
|
||||
if defaultQuota <= 0 {
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
|
||||
v1 "github.com/qianfree/team-api/api/tenant/v1"
|
||||
"github.com/qianfree/team-api/internal/middleware"
|
||||
"github.com/qianfree/team-api/internal/plugin"
|
||||
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
@@ -11,7 +12,7 @@ import (
|
||||
)
|
||||
|
||||
func (s *sTenant) TenantPluginList(ctx context.Context, req *v1.TenantPluginListReq) (*v1.TenantPluginListRes, error) {
|
||||
tenantID := ctxTenantID(ctx)
|
||||
tenantID := middleware.GetTenantID(ctx)
|
||||
entries := plugin.AllPlugins()
|
||||
items := make([]v1.TenantPluginItem, 0, len(entries))
|
||||
|
||||
@@ -44,7 +45,7 @@ func (s *sTenant) TenantPluginList(ctx context.Context, req *v1.TenantPluginList
|
||||
}
|
||||
|
||||
func (s *sTenant) TenantPluginDetail(ctx context.Context, req *v1.TenantPluginDetailReq) (*v1.TenantPluginDetailRes, error) {
|
||||
tenantID := ctxTenantID(ctx)
|
||||
tenantID := middleware.GetTenantID(ctx)
|
||||
entry := plugin.GetPlugin(req.Name)
|
||||
if entry == nil {
|
||||
return nil, gerror.New("插件不存在")
|
||||
@@ -76,7 +77,7 @@ func (s *sTenant) TenantPluginDetail(ctx context.Context, req *v1.TenantPluginDe
|
||||
}
|
||||
|
||||
func (s *sTenant) TenantPluginConfigUpdate(ctx context.Context, req *v1.TenantPluginConfigUpdateReq) (*v1.TenantPluginConfigUpdateRes, error) {
|
||||
tenantID := ctxTenantID(ctx)
|
||||
tenantID := middleware.GetTenantID(ctx)
|
||||
if err := plugin.UpdatePluginConfigForTenant(ctx, req.Name, tenantID, req.Config); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -84,7 +85,7 @@ func (s *sTenant) TenantPluginConfigUpdate(ctx context.Context, req *v1.TenantPl
|
||||
}
|
||||
|
||||
func (s *sTenant) TenantPluginEnable(ctx context.Context, req *v1.TenantPluginEnableReq) (*v1.TenantPluginEnableRes, error) {
|
||||
tenantID := ctxTenantID(ctx)
|
||||
tenantID := middleware.GetTenantID(ctx)
|
||||
if err := plugin.EnableForTenant(ctx, req.Name, tenantID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -92,7 +93,7 @@ func (s *sTenant) TenantPluginEnable(ctx context.Context, req *v1.TenantPluginEn
|
||||
}
|
||||
|
||||
func (s *sTenant) TenantPluginDisable(ctx context.Context, req *v1.TenantPluginDisableReq) (*v1.TenantPluginDisableRes, error) {
|
||||
tenantID := ctxTenantID(ctx)
|
||||
tenantID := middleware.GetTenantID(ctx)
|
||||
if err := plugin.DisableForTenant(ctx, req.Name, tenantID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ import (
|
||||
"github.com/qianfree/team-api/internal/model/do"
|
||||
|
||||
v1 "github.com/qianfree/team-api/api/tenant/v1"
|
||||
"github.com/qianfree/team-api/internal/middleware"
|
||||
)
|
||||
|
||||
type projectRow struct {
|
||||
@@ -49,7 +50,7 @@ type usageLogRow struct {
|
||||
|
||||
// ProjectList returns a paginated list of projects for a tenant.
|
||||
func (s *sTenant) ProjectList(ctx context.Context, req *v1.TenantProjectListReq) (*v1.TenantProjectListRes, error) {
|
||||
tenantID := ctxTenantID(ctx)
|
||||
tenantID := middleware.GetTenantID(ctx)
|
||||
page, pageSize := common.NormalizePagination(req.Page, req.PageSize)
|
||||
|
||||
var projects []projectRow
|
||||
@@ -78,8 +79,8 @@ func (s *sTenant) ProjectList(ctx context.Context, req *v1.TenantProjectListReq)
|
||||
|
||||
// ProjectCreate creates a new project for a tenant.
|
||||
func (s *sTenant) ProjectCreate(ctx context.Context, req *v1.TenantProjectCreateReq) (*v1.TenantProjectCreateRes, error) {
|
||||
tenantID := ctxTenantID(ctx)
|
||||
userID := ctxUserID(ctx)
|
||||
tenantID := middleware.GetTenantID(ctx)
|
||||
userID := middleware.GetUserID(ctx)
|
||||
|
||||
name := req.Name
|
||||
if name == "" {
|
||||
@@ -112,7 +113,7 @@ func (s *sTenant) ProjectCreate(ctx context.Context, req *v1.TenantProjectCreate
|
||||
|
||||
// ProjectUpdate updates a project.
|
||||
func (s *sTenant) ProjectUpdate(ctx context.Context, req *v1.TenantProjectUpdateReq) (*v1.TenantProjectUpdateRes, error) {
|
||||
tenantID := ctxTenantID(ctx)
|
||||
tenantID := middleware.GetTenantID(ctx)
|
||||
|
||||
var project *struct {
|
||||
ID int64 `json:"id"`
|
||||
@@ -161,7 +162,7 @@ func (s *sTenant) ProjectUpdate(ctx context.Context, req *v1.TenantProjectUpdate
|
||||
|
||||
// ProjectArchive archives a project and revokes all its keys.
|
||||
func (s *sTenant) ProjectArchive(ctx context.Context, req *v1.TenantProjectArchiveReq) (*v1.TenantProjectArchiveRes, error) {
|
||||
tenantID := ctxTenantID(ctx)
|
||||
tenantID := middleware.GetTenantID(ctx)
|
||||
|
||||
var project *struct {
|
||||
ID int64 `json:"id"`
|
||||
@@ -203,7 +204,7 @@ func (s *sTenant) ProjectArchive(ctx context.Context, req *v1.TenantProjectArchi
|
||||
|
||||
// ProjectUnarchive restores an archived project. Keys are NOT auto-restored.
|
||||
func (s *sTenant) ProjectUnarchive(ctx context.Context, req *v1.TenantProjectUnarchiveReq) (*v1.TenantProjectUnarchiveRes, error) {
|
||||
tenantID := ctxTenantID(ctx)
|
||||
tenantID := middleware.GetTenantID(ctx)
|
||||
|
||||
var project *struct {
|
||||
ID int64 `json:"id"`
|
||||
@@ -287,7 +288,7 @@ func CheckBudgetExhausted(ctx context.Context) error {
|
||||
|
||||
// ProjectGet 根据 ID 获取单个项目详情(含统计摘要)
|
||||
func (s *sTenant) ProjectGet(ctx context.Context, req *v1.TenantProjectGetReq) (*v1.TenantProjectGetRes, error) {
|
||||
tenantID := ctxTenantID(ctx)
|
||||
tenantID := middleware.GetTenantID(ctx)
|
||||
|
||||
var p *projectRow
|
||||
err := dao.TntProjects.Ctx(ctx).
|
||||
@@ -350,12 +351,12 @@ func (s *sTenant) ProjectGet(ctx context.Context, req *v1.TenantProjectGetReq) (
|
||||
|
||||
// ProjectApiKeyList 获取项目密钥列表(owner/admin 权限)
|
||||
func (s *sTenant) ProjectApiKeyList(ctx context.Context, req *v1.TenantProjectApiKeyListReq) (*v1.TenantProjectApiKeyListRes, error) {
|
||||
role := ctxUserRole(ctx)
|
||||
role := middleware.GetUserRole(ctx)
|
||||
if role != "owner" && role != "admin" {
|
||||
return nil, common.NewForbiddenError("需要 owner 或 admin 权限")
|
||||
}
|
||||
|
||||
tenantID := ctxTenantID(ctx)
|
||||
tenantID := middleware.GetTenantID(ctx)
|
||||
page, pageSize := common.NormalizePagination(req.Page, req.PageSize)
|
||||
|
||||
var keys []apiKeyRow
|
||||
@@ -385,13 +386,13 @@ func (s *sTenant) ProjectApiKeyList(ctx context.Context, req *v1.TenantProjectAp
|
||||
|
||||
// ProjectApiKeyCreate 创建项目密钥(owner/admin 权限)
|
||||
func (s *sTenant) ProjectApiKeyCreate(ctx context.Context, req *v1.TenantProjectApiKeyCreateReq) (*v1.TenantProjectApiKeyCreateRes, error) {
|
||||
role := ctxUserRole(ctx)
|
||||
role := middleware.GetUserRole(ctx)
|
||||
if role != "owner" && role != "admin" {
|
||||
return nil, common.NewForbiddenError("需要 owner 或 admin 权限")
|
||||
}
|
||||
|
||||
tenantID := ctxTenantID(ctx)
|
||||
userID := ctxUserID(ctx)
|
||||
tenantID := middleware.GetTenantID(ctx)
|
||||
userID := middleware.GetUserID(ctx)
|
||||
|
||||
// Verify project exists and belongs to tenant
|
||||
var project *struct {
|
||||
@@ -457,12 +458,12 @@ func (s *sTenant) ProjectApiKeyCreate(ctx context.Context, req *v1.TenantProject
|
||||
|
||||
// ProjectApiKeyDelete 删除项目密钥(owner/admin 权限)
|
||||
func (s *sTenant) ProjectApiKeyDelete(ctx context.Context, req *v1.TenantProjectApiKeyDeleteReq) (*v1.TenantProjectApiKeyDeleteRes, error) {
|
||||
role := ctxUserRole(ctx)
|
||||
role := middleware.GetUserRole(ctx)
|
||||
if role != "owner" && role != "admin" {
|
||||
return nil, common.NewForbiddenError("需要 owner 或 admin 权限")
|
||||
}
|
||||
|
||||
tenantID := ctxTenantID(ctx)
|
||||
tenantID := middleware.GetTenantID(ctx)
|
||||
|
||||
// Verify the key belongs to the project and tenant
|
||||
var key *struct {
|
||||
@@ -495,12 +496,12 @@ func (s *sTenant) ProjectApiKeyDelete(ctx context.Context, req *v1.TenantProject
|
||||
|
||||
// ProjectUsageStats 获取项目用量统计(按日汇总,近30天)(owner/admin 权限)
|
||||
func (s *sTenant) ProjectUsageStats(ctx context.Context, req *v1.TenantProjectUsageStatsReq) (*v1.TenantProjectUsageStatsRes, error) {
|
||||
role := ctxUserRole(ctx)
|
||||
role := middleware.GetUserRole(ctx)
|
||||
if role != "owner" && role != "admin" {
|
||||
return nil, common.NewForbiddenError("需要 owner 或 admin 权限")
|
||||
}
|
||||
|
||||
tenantID := ctxTenantID(ctx)
|
||||
tenantID := middleware.GetTenantID(ctx)
|
||||
|
||||
var project struct {
|
||||
ID int64 `json:"id"`
|
||||
@@ -603,12 +604,12 @@ func (s *sTenant) ProjectUsageStats(ctx context.Context, req *v1.TenantProjectUs
|
||||
|
||||
// ProjectUsageLogs 获取项目用量日志(分页)(owner/admin 权限)
|
||||
func (s *sTenant) ProjectUsageLogs(ctx context.Context, req *v1.TenantProjectUsageLogsReq) (*v1.TenantProjectUsageLogsRes, error) {
|
||||
role := ctxUserRole(ctx)
|
||||
role := middleware.GetUserRole(ctx)
|
||||
if role != "owner" && role != "admin" {
|
||||
return nil, common.NewForbiddenError("需要 owner 或 admin 权限")
|
||||
}
|
||||
|
||||
tenantID := ctxTenantID(ctx)
|
||||
tenantID := middleware.GetTenantID(ctx)
|
||||
page, pageSize := common.NormalizePagination(req.Page, req.PageSize)
|
||||
|
||||
// 获取项目关联的 API Key ID 列表
|
||||
|
||||
@@ -10,13 +10,14 @@ import (
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
|
||||
lcommon "github.com/qianfree/team-api/internal/logic/common"
|
||||
"github.com/qianfree/team-api/internal/middleware"
|
||||
|
||||
v1 "github.com/qianfree/team-api/api/tenant/v1"
|
||||
)
|
||||
|
||||
// ValidatePromoCode 校验优惠码并返回折扣金额
|
||||
func (s *sTenant) ValidatePromoCode(ctx context.Context, req *v1.TenantValidatePromoCodeReq) (*v1.TenantValidatePromoCodeRes, error) {
|
||||
tenantID := ctxTenantID(ctx)
|
||||
tenantID := middleware.GetTenantID(ctx)
|
||||
|
||||
var promo *struct {
|
||||
ID int64 `json:"id"`
|
||||
|
||||
@@ -18,16 +18,17 @@ import (
|
||||
|
||||
v1 "github.com/qianfree/team-api/api/tenant/v1"
|
||||
lcommon2 "github.com/qianfree/team-api/internal/logic/common"
|
||||
"github.com/qianfree/team-api/internal/middleware"
|
||||
)
|
||||
|
||||
// RedeemCode 租户兑换码
|
||||
func (s *sTenant) RedeemCode(ctx context.Context, req *v1.TenantRedeemCodeReq) (*v1.TenantRedeemCodeRes, error) {
|
||||
role := ctxUserRole(ctx)
|
||||
role := middleware.GetUserRole(ctx)
|
||||
if role != "owner" && role != "admin" {
|
||||
return nil, lcommon.NewForbiddenError("需要 owner 或 admin 权限")
|
||||
}
|
||||
tenantID := ctxTenantID(ctx)
|
||||
userID := ctxUserID(ctx)
|
||||
tenantID := middleware.GetTenantID(ctx)
|
||||
userID := middleware.GetUserID(ctx)
|
||||
|
||||
var redemption *struct {
|
||||
ID int64 `json:"id"`
|
||||
@@ -190,11 +191,11 @@ func extendPlanDuration(ctx context.Context, tenantID int64, days int) {
|
||||
|
||||
// ListRedemptionUsages 获取当前租户的兑换历史
|
||||
func (s *sTenant) ListRedemptionUsages(ctx context.Context, req *v1.TenantRedemptionUsagesReq) (*v1.TenantRedemptionUsagesRes, error) {
|
||||
role := ctxUserRole(ctx)
|
||||
role := middleware.GetUserRole(ctx)
|
||||
if role != "owner" && role != "admin" {
|
||||
return nil, lcommon.NewForbiddenError("需要 owner 或 admin 权限")
|
||||
}
|
||||
tenantID := ctxTenantID(ctx)
|
||||
tenantID := middleware.GetTenantID(ctx)
|
||||
page, pageSize := lcommon2.NormalizePagination(req.Page, req.PageSize)
|
||||
|
||||
fromClause := "ord_redemption_usages ru LEFT JOIN ord_redemptions r ON ru.redemption_id = r.id"
|
||||
|
||||
@@ -15,6 +15,7 @@ import (
|
||||
"github.com/qianfree/team-api/internal/consts"
|
||||
"github.com/qianfree/team-api/internal/dao"
|
||||
"github.com/qianfree/team-api/internal/logic/common"
|
||||
"github.com/qianfree/team-api/internal/middleware"
|
||||
do "github.com/qianfree/team-api/internal/model/do"
|
||||
"github.com/qianfree/team-api/internal/model/entity"
|
||||
"github.com/qianfree/team-api/internal/utility/crypto"
|
||||
@@ -105,7 +106,7 @@ func (s *sTenant) Verify2FA(ctx context.Context, req *v1.Tenant2FAVerifyReq) (*v
|
||||
|
||||
// Setup2FA starts the 2FA setup process for the current tenant user.
|
||||
func (s *sTenant) Setup2FA(ctx context.Context, _ *v1.Tenant2FASetupReq) (*v1.Tenant2FASetupRes, error) {
|
||||
userID := ctxUserID(ctx)
|
||||
userID := middleware.GetUserID(ctx)
|
||||
secret, uri, err := common.Setup2FA(ctx, "tenant", userID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -127,7 +128,7 @@ func (s *sTenant) Setup2FA(ctx context.Context, _ *v1.Tenant2FASetupReq) (*v1.Te
|
||||
|
||||
// Enable2FA confirms and enables 2FA.
|
||||
func (s *sTenant) Enable2FA(ctx context.Context, req *v1.Tenant2FAEnableReq) (*v1.Tenant2FAEnableRes, error) {
|
||||
userID := ctxUserID(ctx)
|
||||
userID := middleware.GetUserID(ctx)
|
||||
secret, err := getPendingTOTPSecret(ctx, userID)
|
||||
if err != nil || secret == "" {
|
||||
return nil, common.NewBusinessError(consts.CodeBadRequest, "请先执行2FA设置")
|
||||
@@ -144,7 +145,7 @@ func (s *sTenant) Enable2FA(ctx context.Context, req *v1.Tenant2FAEnableReq) (*v
|
||||
|
||||
// Disable2FA disables 2FA for the current tenant user.
|
||||
func (s *sTenant) Disable2FA(ctx context.Context, req *v1.Tenant2FADisableReq) (*v1.Tenant2FADisableRes, error) {
|
||||
userID := ctxUserID(ctx)
|
||||
userID := middleware.GetUserID(ctx)
|
||||
err := common.Disable2FA(ctx, "tenant", userID, req.Code)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -154,7 +155,7 @@ func (s *sTenant) Disable2FA(ctx context.Context, req *v1.Tenant2FADisableReq) (
|
||||
|
||||
// RegenerateBackupCodes generates new backup codes.
|
||||
func (s *sTenant) RegenerateBackupCodes(ctx context.Context, req *v1.Tenant2FARegenerateBackupCodesReq) (*v1.Tenant2FARegenerateBackupCodesRes, error) {
|
||||
userID := ctxUserID(ctx)
|
||||
userID := middleware.GetUserID(ctx)
|
||||
codes, err := common.RegenerateBackupCodes(ctx, "tenant", userID, req.Code)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -164,7 +165,7 @@ func (s *sTenant) RegenerateBackupCodes(ctx context.Context, req *v1.Tenant2FARe
|
||||
|
||||
// ConfirmHighRisk generates a confirm token for high-risk operations.
|
||||
func (s *sTenant) ConfirmHighRisk(ctx context.Context, req *v1.Tenant2FAConfirmReq) (*v1.Tenant2FAConfirmRes, error) {
|
||||
userID := ctxUserID(ctx)
|
||||
userID := middleware.GetUserID(ctx)
|
||||
|
||||
enabled, err := common.Is2FAEnabled(ctx, "tenant", userID)
|
||||
if err != nil {
|
||||
@@ -193,9 +194,9 @@ func (s *sTenant) ConfirmHighRisk(ctx context.Context, req *v1.Tenant2FAConfirmR
|
||||
// LoginHistory returns the login history for tenant users.
|
||||
// owner/admin see all members in the tenant; member sees only own records.
|
||||
func (s *sTenant) LoginHistory(ctx context.Context, req *v1.TenantLoginHistoryReq) (*v1.TenantLoginHistoryRes, error) {
|
||||
userID := ctxUserID(ctx)
|
||||
tenantID := ctxTenantID(ctx)
|
||||
role := ctxUserRole(ctx)
|
||||
userID := middleware.GetUserID(ctx)
|
||||
tenantID := middleware.GetTenantID(ctx)
|
||||
role := middleware.GetUserRole(ctx)
|
||||
page, pageSize := common.NormalizePagination(req.Page, req.PageSize)
|
||||
|
||||
q := dao.AudLoginHistory.Ctx(ctx).
|
||||
@@ -352,7 +353,7 @@ func getEncKey(ctx context.Context) []byte {
|
||||
|
||||
// GetIPWhitelist returns the tenant's IP whitelist configuration.
|
||||
func (s *sTenant) GetIPWhitelist(ctx context.Context, _ *v1.TenantIPWhitelistGetReq) (*v1.TenantIPWhitelistGetRes, error) {
|
||||
tenantID := ctxTenantID(ctx)
|
||||
tenantID := middleware.GetTenantID(ctx)
|
||||
var tenant *entity.TntTenants
|
||||
err := dao.TntTenants.Ctx(ctx).Where("id", tenantID).Scan(&tenant)
|
||||
if err != nil {
|
||||
@@ -385,7 +386,7 @@ func (s *sTenant) GetIPWhitelist(ctx context.Context, _ *v1.TenantIPWhitelistGet
|
||||
|
||||
// UpdateIPWhitelist updates the tenant's IP whitelist configuration.
|
||||
func (s *sTenant) UpdateIPWhitelist(ctx context.Context, req *v1.TenantIPWhitelistUpdateReq) (*v1.TenantIPWhitelistUpdateRes, error) {
|
||||
tenantID := ctxTenantID(ctx)
|
||||
tenantID := middleware.GetTenantID(ctx)
|
||||
var tenant *entity.TntTenants
|
||||
err := dao.TntTenants.Ctx(ctx).Where("id", tenantID).Scan(&tenant)
|
||||
if err != nil {
|
||||
|
||||
@@ -6,14 +6,15 @@ import (
|
||||
v1 "github.com/qianfree/team-api/api/tenant/v1"
|
||||
"github.com/qianfree/team-api/internal/dao"
|
||||
"github.com/qianfree/team-api/internal/logic/common"
|
||||
"github.com/qianfree/team-api/internal/middleware"
|
||||
)
|
||||
|
||||
// TenantTaskList 租户异步任务列表
|
||||
// owner/admin 可查看租户所有任务,member 只能查看自己的任务
|
||||
func (s *sTenant) TenantTaskList(ctx context.Context, req *v1.TenantTaskListReq) (*v1.TenantTaskListRes, error) {
|
||||
tenantID := ctxTenantID(ctx)
|
||||
role := ctxUserRole(ctx)
|
||||
userID := ctxUserID(ctx)
|
||||
tenantID := middleware.GetTenantID(ctx)
|
||||
role := middleware.GetUserRole(ctx)
|
||||
userID := middleware.GetUserID(ctx)
|
||||
|
||||
m := dao.TskModelTasks.Ctx(ctx).
|
||||
LeftJoin("tnt_users u", "u.id = tsk_model_tasks.user_id AND u.tenant_id = tsk_model_tasks.tenant_id").
|
||||
@@ -101,9 +102,9 @@ func (s *sTenant) TenantTaskList(ctx context.Context, req *v1.TenantTaskListReq)
|
||||
// TenantTaskDetail 租户异步任务详情
|
||||
// owner/admin 可查看租户所有任务,member 只能查看自己的任务
|
||||
func (s *sTenant) TenantTaskDetail(ctx context.Context, req *v1.TenantTaskDetailReq) (*v1.TenantTaskDetailRes, error) {
|
||||
tenantID := ctxTenantID(ctx)
|
||||
role := ctxUserRole(ctx)
|
||||
userID := ctxUserID(ctx)
|
||||
tenantID := middleware.GetTenantID(ctx)
|
||||
role := middleware.GetUserRole(ctx)
|
||||
userID := middleware.GetUserID(ctx)
|
||||
|
||||
var task struct {
|
||||
Id int64 `json:"id"`
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
package tenant
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/qianfree/team-api/internal/service"
|
||||
)
|
||||
|
||||
@@ -17,51 +15,3 @@ func New() *sTenant {
|
||||
func init() {
|
||||
service.RegisterTenant(New())
|
||||
}
|
||||
|
||||
// ctxUserID reads userId from context.
|
||||
func ctxUserID(ctx context.Context) int64 {
|
||||
v := ctx.Value("userId")
|
||||
if v == nil {
|
||||
return 0
|
||||
}
|
||||
if id, ok := v.(int64); ok {
|
||||
return id
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// ctxTenantID reads tenantId from context.
|
||||
func ctxTenantID(ctx context.Context) int64 {
|
||||
v := ctx.Value("tenantId")
|
||||
if v == nil {
|
||||
return 0
|
||||
}
|
||||
if id, ok := v.(int64); ok {
|
||||
return id
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// ctxSessionID reads sessionId from context.
|
||||
func ctxSessionID(ctx context.Context) int64 {
|
||||
v := ctx.Value("sessionId")
|
||||
if v == nil {
|
||||
return 0
|
||||
}
|
||||
if id, ok := v.(int64); ok {
|
||||
return id
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// ctxUserRole reads role from context.
|
||||
func ctxUserRole(ctx context.Context) string {
|
||||
v := ctx.Value("role")
|
||||
if v == nil {
|
||||
return ""
|
||||
}
|
||||
if role, ok := v.(string); ok {
|
||||
return role
|
||||
}
|
||||
return ""
|
||||
}
|
||||
@@ -7,17 +7,17 @@ import (
|
||||
|
||||
v1 "github.com/qianfree/team-api/api/tenant/v1"
|
||||
"github.com/qianfree/team-api/internal/logic/common"
|
||||
"github.com/qianfree/team-api/internal/middleware"
|
||||
|
||||
"github.com/gogf/gf/v2/database/gdb"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/os/gtime"
|
||||
"github.com/qianfree/team-api/internal/utility/export"
|
||||
)
|
||||
|
||||
// TicketCreate 创建工单
|
||||
func (s *sTenant) TicketCreate(ctx context.Context, req *v1.TenantTicketCreateReq) (*v1.TenantTicketCreateRes, error) {
|
||||
tenantID := ctxTenantID(ctx)
|
||||
userID := ctxUserID(ctx)
|
||||
tenantID := middleware.GetTenantID(ctx)
|
||||
userID := middleware.GetUserID(ctx)
|
||||
|
||||
result, err := dao.SptTickets.Ctx(ctx).Insert(do.SptTickets{
|
||||
TenantId: tenantID,
|
||||
@@ -38,8 +38,8 @@ func (s *sTenant) TicketCreate(ctx context.Context, req *v1.TenantTicketCreateRe
|
||||
|
||||
// TicketList 获取租户工单列表
|
||||
func (s *sTenant) TicketList(ctx context.Context, req *v1.TenantTicketListReq) (*v1.TenantTicketListRes, error) {
|
||||
tenantID := ctxTenantID(ctx)
|
||||
userID := ctxUserID(ctx)
|
||||
tenantID := middleware.GetTenantID(ctx)
|
||||
userID := middleware.GetUserID(ctx)
|
||||
|
||||
page, pageSize := common.NormalizePagination(req.Page, req.PageSize)
|
||||
|
||||
@@ -71,8 +71,8 @@ func (s *sTenant) TicketList(ctx context.Context, req *v1.TenantTicketListReq) (
|
||||
|
||||
// TicketGet 获取工单详情(含回复)
|
||||
func (s *sTenant) TicketGet(ctx context.Context, req *v1.TenantTicketGetReq) (*v1.TenantTicketGetRes, error) {
|
||||
tenantID := ctxTenantID(ctx)
|
||||
userID := ctxUserID(ctx)
|
||||
tenantID := middleware.GetTenantID(ctx)
|
||||
userID := middleware.GetUserID(ctx)
|
||||
|
||||
var ticket map[string]any
|
||||
err := dao.SptTickets.Ctx(ctx).
|
||||
@@ -114,8 +114,8 @@ func (s *sTenant) TicketGet(ctx context.Context, req *v1.TenantTicketGetReq) (*v
|
||||
|
||||
// TicketReply 租户用户回复工单
|
||||
func (s *sTenant) TicketReply(ctx context.Context, req *v1.TenantTicketReplyReq) (*v1.TenantTicketReplyRes, error) {
|
||||
tenantID := ctxTenantID(ctx)
|
||||
userID := ctxUserID(ctx)
|
||||
tenantID := middleware.GetTenantID(ctx)
|
||||
userID := middleware.GetUserID(ctx)
|
||||
|
||||
if req.Content == "" {
|
||||
return nil, common.NewBadRequestError("回复内容不能为空")
|
||||
@@ -172,8 +172,8 @@ func (s *sTenant) TicketReply(ctx context.Context, req *v1.TenantTicketReplyReq)
|
||||
|
||||
// TicketClose 租户用户关闭工单
|
||||
func (s *sTenant) TicketClose(ctx context.Context, req *v1.TenantTicketCloseReq) (*v1.TenantTicketCloseRes, error) {
|
||||
tenantID := ctxTenantID(ctx)
|
||||
userID := ctxUserID(ctx)
|
||||
tenantID := middleware.GetTenantID(ctx)
|
||||
userID := middleware.GetUserID(ctx)
|
||||
|
||||
result, err := dao.SptTickets.Ctx(ctx).
|
||||
Where("id", req.Id).
|
||||
@@ -195,8 +195,8 @@ func (s *sTenant) TicketClose(ctx context.Context, req *v1.TenantTicketCloseReq)
|
||||
|
||||
// TicketReopen 租户用户重新打开工单
|
||||
func (s *sTenant) TicketReopen(ctx context.Context, req *v1.TenantTicketReopenReq) (*v1.TenantTicketReopenRes, error) {
|
||||
tenantID := ctxTenantID(ctx)
|
||||
userID := ctxUserID(ctx)
|
||||
tenantID := middleware.GetTenantID(ctx)
|
||||
userID := middleware.GetUserID(ctx)
|
||||
|
||||
result, err := dao.SptTickets.Ctx(ctx).
|
||||
Where("id", req.Id).
|
||||
@@ -218,14 +218,8 @@ func (s *sTenant) TicketReopen(ctx context.Context, req *v1.TenantTicketReopenRe
|
||||
|
||||
// ExportTickets exports the tenant ticket list as CSV or Excel.
|
||||
func (s *sTenant) ExportTickets(ctx context.Context, req *v1.TenantTicketExportReq) (*v1.TenantTicketExportRes, error) {
|
||||
r := g.RequestFromCtx(ctx)
|
||||
format := req.Format
|
||||
if format == "" {
|
||||
format = "csv"
|
||||
}
|
||||
|
||||
tenantID := ctxTenantID(ctx)
|
||||
userID := ctxUserID(ctx)
|
||||
tenantID := middleware.GetTenantID(ctx)
|
||||
userID := middleware.GetUserID(ctx)
|
||||
|
||||
columns := []export.Column{
|
||||
{Field: "id", Header: "ID"},
|
||||
@@ -238,7 +232,7 @@ func (s *sTenant) ExportTickets(ctx context.Context, req *v1.TenantTicketExportR
|
||||
}
|
||||
|
||||
config := export.Config{
|
||||
Format: format,
|
||||
Format: req.Format,
|
||||
Filename: "工单列表_" + gtime.Now().Format("Ymd_His"),
|
||||
Columns: columns,
|
||||
}
|
||||
@@ -255,29 +249,7 @@ func (s *sTenant) ExportTickets(ctx context.Context, req *v1.TenantTicketExportR
|
||||
return query
|
||||
}
|
||||
|
||||
if format == "xlsx" {
|
||||
var items []*v1.TenantTicketItem
|
||||
err := buildQuery().OrderDesc("spt_tickets.created_at").Scan(&items)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
data := make([]map[string]any, 0, len(items))
|
||||
for _, item := range items {
|
||||
data = append(data, map[string]any{
|
||||
"id": item.Id,
|
||||
"category": item.Category,
|
||||
"title": item.Title,
|
||||
"urgency": item.Urgency,
|
||||
"status": item.Status,
|
||||
"assigned_admin_name": item.AssignedAdminName,
|
||||
"created_at": item.CreatedAt,
|
||||
})
|
||||
}
|
||||
return nil, export.WriteExcel(r, config, data)
|
||||
}
|
||||
|
||||
return nil, export.StreamCSV(r, config, func(yield func(map[string]any) bool) {
|
||||
return nil, export.GenericExport(ctx, config, func(yield func(map[string]any) bool) {
|
||||
offset := 0
|
||||
for {
|
||||
var items []*v1.TenantTicketItem
|
||||
|
||||
@@ -147,3 +147,25 @@ func getEncKey(ctx context.Context) []byte {
|
||||
}
|
||||
return crypto.MustGetEncryptionKey(hexKey)
|
||||
}
|
||||
|
||||
// GetOpenTenantID extracts the tenant ID injected by OpenPlatformAuth middleware.
|
||||
func GetOpenTenantID(ctx context.Context) int64 {
|
||||
val := ctx.Value("openTenantId")
|
||||
if val != nil {
|
||||
if id, ok := val.(int64); ok {
|
||||
return id
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// GetOpenAppID extracts the app ID injected by OpenPlatformAuth middleware.
|
||||
func GetOpenAppID(ctx context.Context) int64 {
|
||||
val := ctx.Value("openAppId")
|
||||
if val != nil {
|
||||
if id, ok := val.(int64); ok {
|
||||
return id
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
@@ -1,5 +1,11 @@
|
||||
package export
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
)
|
||||
|
||||
// Column defines one column in the exported file.
|
||||
type Column struct {
|
||||
Field string // Key in the data map
|
||||
@@ -22,3 +28,23 @@ func (c Config) GetMaxRows() int {
|
||||
}
|
||||
return c.MaxRows
|
||||
}
|
||||
|
||||
// GenericExport handles format normalization and xlsx/csv branching.
|
||||
// queryFn yields data rows — same callback as StreamCSV's queryFn.
|
||||
// For xlsx: collects all rows from queryFn, then calls WriteExcel.
|
||||
// For csv: passes queryFn directly to StreamCSV for streaming.
|
||||
func GenericExport(ctx context.Context, config Config, queryFn func(yield func(map[string]any) bool)) error {
|
||||
r := g.RequestFromCtx(ctx)
|
||||
config.Format = detectFormat(config.Format)
|
||||
|
||||
if config.Format == "xlsx" {
|
||||
var data []map[string]any
|
||||
queryFn(func(row map[string]any) bool {
|
||||
data = append(data, row)
|
||||
return true
|
||||
})
|
||||
return WriteExcel(r, config, data)
|
||||
}
|
||||
|
||||
return StreamCSV(r, config, queryFn)
|
||||
}
|
||||
Reference in new issue
Block a user