feat: 添加管理员密码修改功能

This commit is contained in:
2026-04-27 23:16:31 +08:00
parent c161660b51
commit 9c4f22ff97
8 changed files with 135 additions and 0 deletions
+8
View File
@@ -49,12 +49,20 @@ export const useAuthStore = defineStore("auth", () => {
}
}
async function changePassword(currentPassword: string, newPassword: string) {
await apiClient.post("/auth/change-password", {
currentPassword,
newPassword
});
}
return {
token,
user,
isAuthenticated,
login,
logout,
changePassword,
clearSession
};
});
+76
View File
@@ -9,11 +9,15 @@ import type {
WebhookTestResult
} from "@/types";
import { outputFormatLabelMap, recordingTemplateLabelMap, saveModeLabelMap } from "@/types";
import { useAuthStore } from "@/stores/auth";
type ScriptEventType = "live_started" | "live_ended" | "segment_completed";
const authStore = useAuthStore();
const loading = ref(false);
const saving = ref(false);
const changingPassword = ref(false);
const testingEmail = ref(false);
const testingWebhook = ref(false);
const runningRetentionCleanup = ref(false);
@@ -263,6 +267,54 @@ async function loadSettings() {
}
}
const pwdForm = reactive({
currentPassword: "",
newPassword: "",
confirmPassword: ""
});
const pwdRules = {
currentPassword: [{ required: true, message: "请输入当前密码", trigger: "blur" }],
newPassword: [
{ required: true, message: "请输入新密码", trigger: "blur" },
{ min: 6, message: "新密码长度不能少于 6 位", trigger: "blur" }
],
confirmPassword: [
{ required: true, message: "请再次输入新密码", trigger: "blur" },
{
validator: (_rule: unknown, value: string, callback: (error?: Error) => void) => {
if (value !== pwdForm.newPassword) {
callback(new Error("两次输入的密码不一致"));
} else {
callback();
}
},
trigger: "blur"
}
]
};
const pwdFormRef = ref<InstanceType<typeof import("element-plus").ElForm> | null>(null);
async function changePassword() {
const valid = await pwdFormRef.value?.validate().catch(() => false);
if (!valid) return;
changingPassword.value = true;
try {
await authStore.changePassword(pwdForm.currentPassword, pwdForm.newPassword);
ElMessage.success("密码已修改,请妥善保管新密码。");
pwdForm.currentPassword = "";
pwdForm.newPassword = "";
pwdForm.confirmPassword = "";
pwdFormRef.value?.resetFields();
} catch (error) {
ElMessage.error(getApiErrorMessage(error, "密码修改失败,请稍后重试。"));
} finally {
changingPassword.value = false;
}
}
async function saveSettings() {
saving.value = true;
@@ -1370,6 +1422,30 @@ onMounted(loadSettings);
</div>
</el-card>
<el-card class="surface-card settings-card settings-grid__full" shadow="never">
<h3 class="section-title">管理员密码</h3>
<p class="section-subtitle">修改当前登录账户的密码修改后立即生效</p>
<el-form
ref="pwdFormRef"
:model="pwdForm"
:rules="pwdRules"
label-position="top"
style="max-width: 480px;"
>
<el-form-item label="当前密码" prop="currentPassword">
<el-input v-model="pwdForm.currentPassword" type="password" show-password />
</el-form-item>
<el-form-item label="新密码" prop="newPassword">
<el-input v-model="pwdForm.newPassword" type="password" show-password />
</el-form-item>
<el-form-item label="确认新密码" prop="confirmPassword">
<el-input v-model="pwdForm.confirmPassword" type="password" show-password />
</el-form-item>
<el-button type="primary" :loading="changingPassword" @click="changePassword">修改密码</el-button>
</el-form>
</el-card>
<el-card class="surface-card settings-card settings-grid__full" shadow="never">
<h3 class="section-title">Douyin 请求头</h3>
<p class="section-subtitle">这些伪装参数会同时用于 Douyin API 请求和 ffmpeg 输入拉流适合处理直播流读取校验问题</p>
@@ -9,4 +9,6 @@ public interface IAuthService
Task LogoutAsync(string token, CancellationToken cancellationToken = default);
Task<AuthenticatedUser?> ValidateTokenAsync(string token, CancellationToken cancellationToken = default);
Task ChangePasswordAsync(Guid userId, ChangePasswordRequest request, CancellationToken cancellationToken = default);
}
@@ -109,6 +109,8 @@ public interface IUserAccountRepository
Task<UserAccount?> GetByUsernameAsync(string username, CancellationToken cancellationToken = default);
Task AddAsync(UserAccount userAccount, CancellationToken cancellationToken = default);
void Update(UserAccount userAccount);
}
public interface IUserSessionRepository
@@ -28,3 +28,10 @@ public sealed class LoginResponse
public required AuthenticatedUser User { get; init; }
}
public sealed class ChangePasswordRequest
{
public string CurrentPassword { get; set; } = string.Empty;
public string NewPassword { get; set; } = string.Empty;
}
@@ -112,4 +112,31 @@ public sealed class AuthService : IAuthService
ExpiresAt = session.ExpiresAt
};
}
public async Task ChangePasswordAsync(Guid userId, ChangePasswordRequest request, CancellationToken cancellationToken = default)
{
ArgumentNullException.ThrowIfNull(request);
if (string.IsNullOrWhiteSpace(request.CurrentPassword) || string.IsNullOrWhiteSpace(request.NewPassword))
{
throw new InvalidOperationException("当前密码和新密码不能为空。");
}
if (request.NewPassword.Length < 6)
{
throw new InvalidOperationException("新密码长度不能少于 6 位。");
}
var user = await _userAccountRepository.GetByIdAsync(userId, cancellationToken)
?? throw new InvalidOperationException("用户不存在。");
if (!PasswordHasher.Verify(request.CurrentPassword, user.PasswordHash))
{
throw new InvalidOperationException("当前密码不正确。");
}
user.UpdatePassword(PasswordHasher.Hash(request.NewPassword));
_userAccountRepository.Update(user);
await _unitOfWork.SaveChangesAsync(cancellationToken);
}
}
@@ -323,6 +323,9 @@ public sealed class UserAccountRepository : IUserAccountRepository
public Task AddAsync(UserAccount userAccount, CancellationToken cancellationToken = default) =>
_dbContext.UserAccounts.AddAsync(userAccount, cancellationToken).AsTask();
public void Update(UserAccount userAccount) =>
_dbContext.UserAccounts.Update(userAccount);
}
public sealed class UserSessionRepository : IUserSessionRepository
@@ -33,4 +33,14 @@ public sealed class AuthController : ControllerBase
await _authService.LogoutAsync(token, cancellationToken);
return NoContent();
}
[HttpPost("change-password")]
public async Task<IActionResult> ChangePassword([FromBody] ChangePasswordRequest request, CancellationToken cancellationToken)
{
var currentUser = HttpContext.Items["CurrentUser"] as AuthenticatedUser
?? throw new InvalidOperationException("未登录。");
await _authService.ChangePasswordAsync(currentUser.UserId, request, cancellationToken);
return NoContent();
}
}