feat: 新增账号状态

This commit is contained in:
zerosaturation 2026-04-27 12:59:19 +08:00
parent f6e1caad8b
commit 112d3907be
6 changed files with 132 additions and 22 deletions

View File

@ -170,27 +170,28 @@ func HandleError(c *gin.Context, err error) {
} }
msg := CleanErrorMessage(err) msg := CleanErrorMessage(err)
msgLower := strings.ToLower(msg)
// 根据错误类型返回对应的 HTTP 状态码 // 根据错误类型返回对应的 HTTP 状态码
switch { switch {
case strings.Contains(msgLower, "not found") || strings.Contains(msgLower, "不存在"): case strings.Contains(msg, "not found") || strings.Contains(msg, "不存在"):
NotFound(c, msg) NotFound(c, msg)
case strings.Contains(msgLower, "unauthorized") || case strings.Contains(msg, "unauthorized") ||
strings.Contains(msgLower, "token") || strings.Contains(msg, "token") ||
strings.Contains(msgLower, "请先登录"): strings.Contains(msg, "请先登录"):
Unauthorized(c, msg) Unauthorized(c, msg)
case strings.Contains(msgLower, "invalid") || case strings.Contains(msg, "invalid") ||
strings.Contains(msgLower, "格式不正确") || strings.Contains(msg, "格式不正确") ||
strings.Contains(msgLower, "过长") || strings.Contains(msg, "过长") ||
strings.Contains(msgLower, "过短"): strings.Contains(msg, "过短"):
BadRequest(c, msg) BadRequest(c, msg)
case strings.Contains(msgLower, "already exists") || case strings.Contains(msg, "already exists") ||
strings.Contains(msgLower, "已被注册"): strings.Contains(msg, "已被注册"):
Error(c, http.StatusConflict, msg) Error(c, http.StatusConflict, msg)
case strings.Contains(msgLower, "forbidden") || case strings.Contains(msg, "forbidden") ||
strings.Contains(msgLower, "permission denied") || strings.Contains(msg, "permission denied") ||
strings.Contains(msgLower, "权限不足"): strings.Contains(msg, "权限不足") ||
strings.Contains(msg, "账号已被冻结") ||
strings.Contains(msg, "账号已被封禁"):
Forbidden(c, msg) Forbidden(c, msg)
default: default:
InternalError(c, msg) InternalError(c, msg)

View File

@ -55,6 +55,10 @@ var (
ErrInvalidAssetStatus = errors.New("资产状态无效") ErrInvalidAssetStatus = errors.New("资产状态无效")
ErrInvalidMintOrderStatus = errors.New("订单状态无效") ErrInvalidMintOrderStatus = errors.New("订单状态无效")
// 账号状态相关错误
ErrAccountFrozen = errors.New("账号已被冻结")
ErrAccountBanned = errors.New("账号已被封禁")
// 活动服务相关错误 // 活动服务相关错误
ErrActivityNotFound = errors.New("活动不存在") ErrActivityNotFound = errors.New("活动不存在")
ErrActivityItemNotFound = errors.New("活动道具不存在") ErrActivityItemNotFound = errors.New("活动道具不存在")
@ -79,7 +83,7 @@ func ToStatusCode(err error) pb.StatusCode {
return pb.StatusCode_STATUS_BAD_REQUEST return pb.StatusCode_STATUS_BAD_REQUEST
case ErrInvalidPassword, ErrInvalidToken, ErrTokenExpired, ErrTokenMismatch: case ErrInvalidPassword, ErrInvalidToken, ErrTokenExpired, ErrTokenMismatch:
return pb.StatusCode_STATUS_UNAUTHORIZED return pb.StatusCode_STATUS_UNAUTHORIZED
case ErrUserInactive: case ErrAccountFrozen, ErrAccountBanned:
return pb.StatusCode_STATUS_FORBIDDEN return pb.StatusCode_STATUS_FORBIDDEN
case ErrInvalidMobile, ErrPasswordTooShort, ErrInvalidStarID, ErrInvalidUserID, ErrMaxIdentitiesReached, ErrInvalidNickname: case ErrInvalidMobile, ErrPasswordTooShort, ErrInvalidStarID, ErrInvalidUserID, ErrMaxIdentitiesReached, ErrInvalidNickname:
return pb.StatusCode_STATUS_BAD_REQUEST return pb.StatusCode_STATUS_BAD_REQUEST

View File

@ -0,0 +1,67 @@
package models
import (
"time"
"gorm.io/gorm"
)
// UserAccountStatus 用户账号状态表模型
type UserAccountStatus struct {
ID int64 `gorm:"primaryKey;autoIncrement;column:id"`
UserID int64 `gorm:"not null;uniqueIndex:uk_user_account_status_user_id;column:user_id"`
Status string `gorm:"type:varchar(20);not null;column:status"` // frozen/normal/banned
Reason *string `gorm:"type:text;column:reason"` // 冻结/封号原因
FrozenUntil *int64 `gorm:"column:frozen_until"` // 冻结解封时间(毫秒时间戳)
CreatedAt int64 `gorm:"not null;column:created_at"`
UpdatedAt int64 `gorm:"not null;column:updated_at"`
}
// TableName 指定表名
func (UserAccountStatus) TableName() string {
return "user_account_status"
}
// BeforeCreate 创建前钩子
func (u *UserAccountStatus) BeforeCreate(tx *gorm.DB) error {
now := time.Now().UnixMilli()
u.CreatedAt = now
u.UpdatedAt = now
return nil
}
// BeforeUpdate 更新前钩子
func (u *UserAccountStatus) BeforeUpdate(tx *gorm.DB) error {
u.UpdatedAt = time.Now().UnixMilli()
return nil
}
// Status constants
const (
AccountStatusNormal = "normal"
AccountStatusFrozen = "frozen"
AccountStatusBanned = "banned"
)
// IsNormal 判断账号状态是否正常
func (u *UserAccountStatus) IsNormal() bool {
return u.Status == AccountStatusNormal
}
// IsFrozen 判断账号是否被冻结
func (u *UserAccountStatus) IsFrozen() bool {
return u.Status == AccountStatusFrozen
}
// IsBanned 判断账号是否被封号
func (u *UserAccountStatus) IsBanned() bool {
return u.Status == AccountStatusBanned
}
// GetFrozenUntilTime 返回冻结解封时间(如果是被冻结状态)
func (u *UserAccountStatus) GetFrozenUntilTime() time.Time {
if u.FrozenUntil != nil {
return time.UnixMilli(*u.FrozenUntil)
}
return time.Time{}
}

View File

@ -38,6 +38,9 @@ type UserRepository interface {
// UpdateAvatar 更新用户头像 // UpdateAvatar 更新用户头像
UpdateAvatar(userID int64, avatarURL string) error UpdateAvatar(userID int64, avatarURL string) error
// GetAccountStatus 获取用户账号状态
GetAccountStatus(userID int64) (*models.UserAccountStatus, error)
} }
// userRepository 用户Repository实现 // userRepository 用户Repository实现
@ -228,3 +231,20 @@ func (r *userRepository) UpdateAvatar(userID int64, avatarURL string) error {
return nil return nil
} }
// GetAccountStatus 获取用户账号状态
func (r *userRepository) GetAccountStatus(userID int64) (*models.UserAccountStatus, error) {
if userID <= 0 {
return nil, errors.New("invalid user id")
}
var status models.UserAccountStatus
if err := r.db.Where("user_id = ?", userID).First(&status).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, nil // 没有记录表示正常
}
return nil, err
}
return &status, nil
}

View File

@ -106,6 +106,7 @@
<script setup> <script setup>
import { ref } from 'vue'; import { ref } from 'vue';
import { onLoad } from '@dcloudio/uni-app';
import { useStore } from 'vuex'; import { useStore } from 'vuex';
import { validatePhone, validatePassword } from '@/utils/validator'; import { validatePhone, validatePassword } from '@/utils/validator';
import { AGREEMENT_CONTENT } from '@/utils/agreement'; import { AGREEMENT_CONTENT } from '@/utils/agreement';
@ -125,6 +126,22 @@ const agreementContent = ref('');
const showAgreementDialog = ref(false); const showAgreementDialog = ref(false);
const showTipDialog = ref(false); const showTipDialog = ref(false);
//
const getPageParams = () => {
const pages = getCurrentPages()
if (pages.length > 0) {
const currentPage = pages[pages.length - 1]
if (currentPage.options && currentPage.options.error) {
errorMessage.value = decodeURIComponent(currentPage.options.error)
}
}
}
//
onLoad(() => {
getPageParams()
})
// //
const getAgreementContent = () => { const getAgreementContent = () => {
return AGREEMENT_CONTENT; return AGREEMENT_CONTENT;

View File

@ -1,7 +1,7 @@
// API 基础配置 // API 基础配置
const baseURL = 'http://101.132.250.62:8080' // const baseURL = 'http://101.132.250.62:8080'
// const baseURL = 'http://192.168.110.60:8080' // const baseURL = 'http://192.168.110.60:8080'
// const baseURL = 'http://localhost:8080' const baseURL = 'http://localhost:8080'
// 是否使用模拟数据(开发调试时设为 true后端API准备好后改为 false // 是否使用模拟数据(开发调试时设为 true后端API准备好后改为 false
const USE_MOCK_API = false const USE_MOCK_API = false
@ -60,17 +60,18 @@ export function request(options) {
if (res.data && res.data.code !== undefined) { if (res.data && res.data.code !== undefined) {
if (res.data.code === 200) { if (res.data.code === 200) {
resolve(res.data) resolve(res.data)
} else if (res.data.code === 401 || res.data.code === 400 || res.data } else if (res.data.code === 401 || res.data.code === 400 || res.data.code === 403) {
.code === 403) { // 业务状态码401/400/403未授权/冻结/封号),清除缓存并跳转到登录页
// 业务状态码401未授权清除缓存并跳转到登录页
uni.removeStorageSync('access_token') uni.removeStorageSync('access_token')
uni.removeStorageSync('user') uni.removeStorageSync('user')
// 保留错误消息用于显示
const errorMsg = res.data.message || '登录已过期,请重新登录'
uni.reLaunch({ uni.reLaunch({
url: '/pages/login/login' url: '/pages/login/login?error=' + encodeURIComponent(errorMsg)
}) })
reject(new Error('登录已过期,请重新登录')) reject(new Error(errorMsg))
return return
} else { } else {
// 其他业务错误,返回包含 message 的错误 // 其他业务错误,返回包含 message 的错误