chore: 支持设置初始化密码

This commit is contained in:
xiaojunnuo
2026-05-15 00:26:24 +08:00
parent a815d0245b
commit 3b72ca09c6
11 changed files with 243 additions and 107 deletions
+3 -3
View File
@@ -105,7 +105,7 @@ Certd 是一个支持私有化部署的 SSL/TLS 证书自动化管理平台。
- 前端 `pnpm dev`:启动 Vite 开发服务
- 前端 `pnpm build`:生产构建
- 前端 `pnpm tsc`类型检查
- 不要运行前端 `pnpm tsc` / `vue-tsc`:当前依赖组合中 `vue-tsc@1.8.27` 会直接抛内部错误 `Search string not found: "/supportedTSExtensions = .*(?=;)/"`,不是有效的项目类型检查结果。
- 前端暂不跑单元测试;当前 `test:unit` 只是占位脚本
## 流水线与插件模型
@@ -216,5 +216,5 @@ Get-ChildItem packages\ui\certd-client\src\views\certd
- 后端纯单元测试用例放在 `src` 目录内,并尽量与被测文件相邻,例如 `src/utils/random.test.ts`;对应 `test:unit` 只跑 `src/**/*.test.ts`,构建/打包配置应排除这些 `*.test.ts` 文件。
- 单元测试需要 mock ESM 静态 import 时,优先使用 `esmock`,不要为了测试把业务代码改成构造函数注入或把逻辑挪到调用方;各包 `test:unit` 脚本应显式设置 `NODE_ENV=unittest`
- 单个 monorepo 包运行单元测试时,优先使用 `corepack pnpm --dir <包目录> test:unit`,例如 `corepack pnpm --dir packages\ui\certd-server test:unit``corepack pnpm --dir packages\core\basic test:unit``corepack pnpm --dir packages\plugins\plugin-lib test:unit`;也可以用包名过滤,例如 `corepack pnpm --filter @certd/ui-server test:unit`。前端 `packages\ui\certd-client` 暂时不跑单元测试。
- 前端 TS/Vue/locale 等文件改动后,优先只对本次改动文件运行项目现有自动格式化/修复;Windows/PowerShell 下 Prettier 已验证可用命令为 `packages\ui\certd-client\node_modules\.bin\prettier.cmd --write <files>`ESLint 可用命令为 `packages\ui\certd-client\node_modules\.bin\eslint.cmd --fix <files>`;不要为了格式化无关文件而扩大 diff。项目保留了 `tslint` 依赖,但当前主要使用 ESLint + Prettier。
- 优先对改动包运行聚焦的测试或类型检查;只有跨包影响明显时再考虑全 monorepo 构建。
- 前端 TS/Vue/locale 等文件改动后,优先只对本次改动文件运行项目现有自动格式化/修复;Windows/PowerShell 下 Prettier 已验证可用命令为 `packages\ui\certd-client\node_modules\.bin\prettier.cmd --write <files>`ESLint 可用命令为 `packages\ui\certd-client\node_modules\.bin\eslint.cmd --fix <files>`;不要运行 `vue-tsc` / `pnpm tsc`;不要为了格式化无关文件而扩大 diff。项目保留了 `tslint` 依赖,但当前主要使用 ESLint + Prettier。
- 优先对改动包运行聚焦的测试;后端可按包运行单元测试,前端优先使用 Prettier/ESLint 做改动文件验证。只有跨包影响明显时再考虑全 monorepo 构建。
+1
View File
@@ -38,6 +38,7 @@ Certd® 是一个免费的全自动证书管理系统,让你的网站证书永
* **开放接口支持**: 提供RESTful API接口,方便集成到其他系统
* **站点证书监控**: 定时监控网站证书的过期时间
* **多用户管理**: 用户可以管理自己的证书流水线
* **项目管理**: 企业级项目管理模式
* **多语言支持** 中英双语切换
* **无忧升级** 版本向下兼容
@@ -78,8 +78,13 @@ export default {
passkeyRegisterFailed: "Passkey registration failed",
title: "Change Password",
weakPasswordWarning: "For your account security, please change your password immediately",
initPasswordWarning: "This account does not have a login password yet. Please set one first",
initPasswordTitle: "Set Password",
changeNow: "Change Now",
setNow: "Set Now",
notNow: "Not Now",
successMessage: "Changed successfully",
initPasswordSuccessMessage: "Set successfully",
oldPassword: "Old Password",
oldPasswordRequired: "Please enter the old password",
newPassword: "New Password",
@@ -79,8 +79,13 @@ export default {
title: "修改密码",
weakPasswordWarning: "为了您的账户安全,请立即修改密码",
initPasswordWarning: "当前账号还未设置登录密码,请先设置密码",
initPasswordTitle: "设置密码",
changeNow: "立即修改",
setNow: "立即设置",
notNow: "暂不设置",
successMessage: "修改成功",
initPasswordSuccessMessage: "设置成功",
oldPassword: "旧密码",
oldPasswordRequired: "请输入旧密码",
newPassword: "新密码",
@@ -38,6 +38,7 @@ export interface UserInfoRes {
avatar?: string;
roleIds: number[];
isWeak?: boolean;
needInitPassword?: boolean;
validTime?: number;
status?: number;
}
@@ -15,6 +15,14 @@ export async function changePassword(form: any) {
});
}
export async function initPassword(form: any) {
return await request({
url: "/mine/initPassword",
method: "POST",
data: form,
});
}
export async function UpdateProfile(form: any) {
return await request({
url: "/mine/updateProfile",
@@ -9,7 +9,7 @@ import { ref } from "vue";
import { useI18n } from "/src/locales";
const { t } = useI18n();
import { CrudOptions, useColumns, useFormWrapper } from "@fast-crud/fast-crud";
import { compute, CrudOptions, useColumns, useFormWrapper } from "@fast-crud/fast-crud";
import * as api from "/@/views/certd/mine/api";
import { notification } from "ant-design-vue";
import { useUserStore } from "/@/store/user";
@@ -20,6 +20,11 @@ defineProps<{
let passwordFormRef = ref();
type OpenOptions = {
password?: string;
init?: boolean;
};
const validatePass1 = async (rule: any, value: any) => {
if (value === "") {
throw new Error(t("authentication.enterPassword"));
@@ -53,19 +58,33 @@ const passwordFormOptions: CrudOptions = {
width: "500px",
},
async doSubmit({ form }) {
await api.changePassword(form);
if (form.init) {
await api.initPassword(form);
} else {
await api.changePassword(form);
}
//重新加载用户信息
await userStore.loadUserInfo();
},
async afterSubmit() {
notification.success({ message: t("authentication.successMessage") });
const formData = passwordFormRef.value?.getFormData?.();
const message = formData?.init ? t("authentication.initPasswordSuccessMessage") : t("authentication.successMessage");
notification.success({ message });
},
},
columns: {
init: {
title: "init",
type: "text",
form: {
show: false,
},
},
password: {
title: t("authentication.oldPassword"),
type: "password",
form: {
show: compute(({ form }) => form.init !== true),
rules: [{ required: true, message: t("authentication.oldPasswordRequired") }],
},
},
@@ -97,12 +116,16 @@ const passwordFormOptions: CrudOptions = {
},
};
async function open(opts: { password: "" }) {
async function open(opts: OpenOptions = {}) {
const formOptions = buildFormOptions(passwordFormOptions);
formOptions.newInstance = true; //新实例打开
if (opts.init) {
formOptions.wrapper.title = t("authentication.initPasswordTitle");
}
passwordFormRef.value = await openDialog(formOptions);
passwordFormRef.value.setFormData({
password: opts.password,
init: opts.init === true,
password: opts.password || "",
});
console.log(passwordFormRef.value);
}
@@ -48,7 +48,7 @@
</div>
</div>
<div class="action-buttons gap-2">
<change-password-button :show-button="true" />
<change-password-button ref="changePasswordButtonRef" :show-button="true" />
<a-button type="primary" class="action-btn" @click="goSecuritySetting">
{{ t("authentication.securitySettingTip") }}
@@ -387,6 +387,7 @@ const checkPasskeySupport = () => {
}
};
const userStore = useUserStore();
const changePasswordButtonRef = ref();
const userAvatar = computed(() => {
if (isEmpty(userInfo.value.avatar)) {
return "";
@@ -400,6 +401,21 @@ const userAvatar = computed(() => {
onMounted(async () => {
await getUserInfo();
userStore.setUserInfo(userInfo.value);
if (userInfo.value.needInitPassword === true) {
Modal.confirm({
title: t("authentication.initPasswordTitle"),
content: t("authentication.initPasswordWarning"),
okText: t("authentication.setNow"),
cancelText: t("authentication.notNow"),
closable: true,
onOk: () => {
changePasswordButtonRef.value.open({
init: true,
});
},
});
}
await loadContactCapability();
await loadOauthBounds();
await loadOauthProviders();
@@ -1,16 +1,16 @@
import { BaseController, Constants, SysSettingsService } from '@certd/lib-server';
import { ALL, Body, Controller, Inject, Post, Provide } from '@midwayjs/core';
import { PasskeyService } from '../../../modules/login/service/passkey-service.js';
import { RoleService } from '../../../modules/sys/authority/service/role-service.js';
import { UserService } from '../../../modules/sys/authority/service/user-service.js';
import { ApiTags } from '@midwayjs/swagger';
import { CodeService } from '../../../modules/basic/service/code-service.js';
import { BaseController, Constants, SysSettingsService } from "@certd/lib-server";
import { ALL, Body, Controller, Inject, Post, Provide } from "@midwayjs/core";
import { PasskeyService } from "../../../modules/login/service/passkey-service.js";
import { RoleService } from "../../../modules/sys/authority/service/role-service.js";
import { UserService } from "../../../modules/sys/authority/service/user-service.js";
import { ApiTags } from "@midwayjs/swagger";
import { CodeService } from "../../../modules/basic/service/code-service.js";
/**
*/
@Provide()
@Controller('/api/mine')
@ApiTags(['mine'])
@Controller("/api/mine")
@ApiTags(["mine"])
export class MineController extends BaseController {
@Inject()
userService: UserService;
@@ -27,28 +27,38 @@ export class MineController extends BaseController {
@Inject()
sysSettingsService: SysSettingsService;
@Post('/info', { description: Constants.per.authOnly, summary: '查询用户信息' })
@Post("/info", { description: Constants.per.authOnly, summary: "查询用户信息" })
public async info() {
const userId = this.getUserId();
const user = await this.userService.info(userId);
const isWeak = await this.userService.checkPassword('123456', user.password, user.passwordVersion);
const isWeak = await this.userService.checkPassword("123456", user.password, user.passwordVersion);
if (isWeak) {
//@ts-ignore
user.isWeak = true;
}
const needInitPassword = user.password === "changeme";
user.roleIds = await this.roleService.getRoleIdsByUserId(userId);
delete user.password;
//@ts-ignore
user.needInitPassword = needInitPassword;
return this.ok(user);
}
@Post('/changePassword', { description: Constants.per.authOnly, summary: '修改密码' })
@Post("/changePassword", { description: Constants.per.authOnly, summary: "修改密码" })
public async changePassword(@Body(ALL) body: any) {
const userId = this.getUserId();
await this.userService.changePassword(userId, body);
return this.ok({});
}
@Post('/updateProfile', { description: Constants.per.authOnly, summary: '更新用户资料' })
@Post("/initPassword", { description: Constants.per.authOnly, summary: "初始化密码" })
public async initPassword(@Body(ALL) body: any) {
const userId = this.getUserId();
await this.userService.initPassword(userId, body);
return this.ok({});
}
@Post("/updateProfile", { description: Constants.per.authOnly, summary: "更新用户资料" })
public async updateProfile(@Body(ALL) body: any) {
const userId = this.getUserId();
@@ -59,7 +69,7 @@ export class MineController extends BaseController {
return this.ok({});
}
@Post('/contact/capability', { description: Constants.per.authOnly, summary: '查询联系方式绑定能力' })
@Post("/contact/capability", { description: Constants.per.authOnly, summary: "查询联系方式绑定能力" })
public async contactCapability() {
const settings = await this.sysSettingsService.getPrivateSettings();
return this.ok({
@@ -67,27 +77,27 @@ export class MineController extends BaseController {
});
}
@Post('/contact/verifyIdentity', { description: Constants.per.authOnly, summary: '验证本人操作' })
public async verifyContactIdentity(@Body(ALL) body: { identityType: 'password' | 'email' | 'mobile'; identityPassword?: string; identityValidateCode?: string }) {
@Post("/contact/verifyIdentity", { description: Constants.per.authOnly, summary: "验证本人操作" })
public async verifyContactIdentity(@Body(ALL) body: { identityType: "password" | "email" | "mobile"; identityPassword?: string; identityValidateCode?: string }) {
const userId = this.getUserId();
await this.userService.verifyIdentity(userId, body, this.codeService);
const validationCode = this.codeService.setValidationValue({
type: 'contactIdentity',
type: "contactIdentity",
userId,
identityType: body.identityType,
});
return this.ok({ validationCode });
}
@Post('/contact/mobile', { description: Constants.per.authOnly, summary: '绑定或修改手机号' })
@Post("/contact/mobile", { description: Constants.per.authOnly, summary: "绑定或修改手机号" })
public async updateMobile(@Body(ALL) body: { phoneCode?: string; mobile: string; validateCode: string; identityValidationCode: string }) {
const userId = this.getUserId();
this.userService.checkContactIdentityValidation(userId, body.identityValidationCode, this.codeService);
await this.codeService.checkSmsCode({
mobile: body.mobile,
phoneCode: body.phoneCode || '86',
phoneCode: body.phoneCode || "86",
smsCode: body.validateCode,
verificationType: 'bindMobile',
verificationType: "bindMobile",
throwError: true,
});
await this.userService.updateMobile(userId, {
@@ -97,14 +107,14 @@ export class MineController extends BaseController {
return this.ok({});
}
@Post('/contact/email', { description: Constants.per.authOnly, summary: '绑定或修改邮箱' })
@Post("/contact/email", { description: Constants.per.authOnly, summary: "绑定或修改邮箱" })
public async updateEmail(@Body(ALL) body: { email: string; validateCode: string; identityValidationCode: string }) {
const userId = this.getUserId();
this.userService.checkContactIdentityValidation(userId, body.identityValidationCode, this.codeService);
this.codeService.checkEmailCode({
email: body.email,
validateCode: body.validateCode,
verificationType: 'bindEmail',
verificationType: "bindEmail",
throwError: true,
});
await this.userService.updateEmail(userId, {
@@ -0,0 +1,51 @@
/// <reference types="mocha" />
import assert from "node:assert/strict";
import { UserService } from "./user-service.js";
describe("UserService.initPassword", () => {
function createService(user: any) {
const service = new UserService();
service.info = async () => user;
let updatedParam: any;
service.update = async (param: any) => {
updatedParam = param;
};
return { service, getUpdatedParam: () => updatedParam };
}
it("sets a new password when current password is changeme", async () => {
const { service, getUpdatedParam } = createService({
id: 12,
password: "changeme",
passwordVersion: 2,
});
await service.initPassword(12, {
newPassword: "new-password",
confirmNewPassword: "new-password",
});
assert.deepEqual(getUpdatedParam(), {
id: 12,
password: "new-password",
});
});
it("rejects initPassword after password has already been set", async () => {
const { service } = createService({
id: 12,
password: "$2a$10$already-hashed",
passwordVersion: 2,
});
await assert.rejects(
() =>
service.initPassword(12, {
newPassword: "new-password",
confirmNewPassword: "new-password",
}),
/当前账号已设置密码/
);
});
});
@@ -1,22 +1,22 @@
import { Inject, Provide, Scope, ScopeEnum } from '@midwayjs/core';
import { InjectEntityModel } from '@midwayjs/typeorm';
import { EntityManager, In, MoreThan, Not, Repository } from 'typeorm';
import { UserEntity } from '../entity/user.js';
import * as _ from 'lodash-es';
import { BaseService, CommonException, Constants, FileService, SysInstallInfo, SysSettingsService } from '@certd/lib-server';
import { RoleService } from './role-service.js';
import { PermissionService } from './permission-service.js';
import { UserRoleService } from './user-role-service.js';
import { UserRoleEntity } from '../entity/user-role.js';
import bcrypt from 'bcryptjs';
import { RandomUtil } from '../../../../utils/random.js';
import dayjs from 'dayjs';
import { DbAdapter } from '../../../db/index.js';
import { simpleNanoId, utils } from '@certd/basic';
import { OauthBoundService } from '../../../login/service/oauth-bound-service.js';
import { Inject, Provide, Scope, ScopeEnum } from "@midwayjs/core";
import { InjectEntityModel } from "@midwayjs/typeorm";
import { EntityManager, In, MoreThan, Not, Repository } from "typeorm";
import { UserEntity } from "../entity/user.js";
import * as _ from "lodash-es";
import { BaseService, CommonException, Constants, FileService, SysInstallInfo, SysSettingsService } from "@certd/lib-server";
import { RoleService } from "./role-service.js";
import { PermissionService } from "./permission-service.js";
import { UserRoleService } from "./user-role-service.js";
import { UserRoleEntity } from "../entity/user-role.js";
import bcrypt from "bcryptjs";
import { RandomUtil } from "../../../../utils/random.js";
import dayjs from "dayjs";
import { DbAdapter } from "../../../db/index.js";
import { simpleNanoId, utils } from "@certd/basic";
import { OauthBoundService } from "../../../login/service/oauth-bound-service.js";
export type RegisterType = 'username' | 'mobile' | 'email';
export type ForgotPasswordType = 'mobile' | 'email';
export type RegisterType = "username" | "mobile" | "email";
export type ForgotPasswordType = "mobile" | "email";
export const AdminRoleId = 1;
@@ -83,14 +83,14 @@ export class UserService extends BaseService<UserEntity> {
},
});
if (!_.isEmpty(exists)) {
throw new CommonException('用户名已经存在');
throw new CommonException("用户名已经存在");
}
const plainPassword = param.password ?? RandomUtil.randomStr(6);
param.passwordVersion = 2;
param.password = await this.genPassword(plainPassword, param.passwordVersion); // 默认密码 建议未改密码不能登陆
if (param.avatar) {
param.avatar = await this.fileService.saveFile(0, param.avatar, 'public');
param.avatar = await this.fileService.saveFile(0, param.avatar, "public");
}
await super.add(param);
@@ -107,13 +107,13 @@ export class UserService extends BaseService<UserEntity> {
*/
async update(param) {
if (param.id == null) {
throw new CommonException('id不能为空');
throw new CommonException("id不能为空");
}
const userInfo = await this.repository.findOne({
where: { id: param.id },
});
if (!userInfo) {
throw new CommonException('用户不存在');
throw new CommonException("用户不存在");
}
if (param.username) {
@@ -125,7 +125,7 @@ export class UserService extends BaseService<UserEntity> {
{ email: username, id: Not(id) },
]);
if (old != null) {
throw new CommonException('用户名已被占用');
throw new CommonException("用户名已被占用");
}
}
if (!_.isEmpty(param.password)) {
@@ -136,7 +136,7 @@ export class UserService extends BaseService<UserEntity> {
}
if (param.avatar) {
param.avatar = await this.fileService.saveFile(userInfo.id, param.avatar, 'public');
param.avatar = await this.fileService.saveFile(userInfo.id, param.avatar, "public");
}
await super.update(param);
await this.roleService.updateRoles(param.id, param.roles);
@@ -168,7 +168,7 @@ export class UserService extends BaseService<UserEntity> {
async buildPlainPassword(rawPassword: string) {
const setting: SysInstallInfo = await this.sysSettingsService.getSetting(SysInstallInfo);
if (!setting.siteId) {
throw new CommonException('站点ID还未初始化');
throw new CommonException("站点ID还未初始化");
}
const prefixSiteId = setting.siteId.substring(1, 5);
return rawPassword + prefixSiteId;
@@ -192,48 +192,47 @@ export class UserService extends BaseService<UserEntity> {
const username = user.username;
const old = await this.findOne([{ username: username }, { mobile: username }, { email: username }]);
if (old != null) {
throw new CommonException('用户名已被注册');
throw new CommonException("用户名已被注册");
}
}
if (user.mobile) {
const mobile = user.mobile;
user.nickName = user.username || mobile.substring(0, 3) + '****' + mobile.substring(7);
user.nickName = user.username || mobile.substring(0, 3) + "****" + mobile.substring(7);
const old = await this.findOne([{ username: mobile }, { mobile: mobile }, { email: mobile }]);
if (old != null) {
throw new CommonException('手机号已被注册');
throw new CommonException("手机号已被注册");
}
}
if (user.email) {
const email = user.email;
const old = await this.findOne([{ username: email }, { mobile: email }, { email: email }]);
if (old != null) {
throw new CommonException('邮箱已被注册');
throw new CommonException("邮箱已被注册");
}
}
if (!user.username) {
user.username = 'user_' + simpleNanoId();
user.username = "user_" + simpleNanoId();
}
let newUser: UserEntity = UserEntity.of({
username: user.username,
password: user.password,
email: user.email || '',
mobile: user.mobile || '',
email: user.email || "",
mobile: user.mobile || "",
nickName: user.nickName || user.username,
avatar: user.avatar || '',
phoneCode: user.phoneCode || '86',
avatar: user.avatar || "",
phoneCode: user.phoneCode || "86",
status: 1,
passwordVersion: 2,
});
if (!newUser.password) {
newUser.password = "changeme";
}else{
} else {
newUser.password = await this.genPassword(newUser.password, newUser.passwordVersion);
}
await this.transaction(async txManager => {
newUser = await txManager.save(newUser);
@@ -248,28 +247,28 @@ export class UserService extends BaseService<UserEntity> {
delete newUser.password;
utils.mitter.emit('register', { userId: newUser.id });
utils.mitter.emit("register", { userId: newUser.id });
return newUser;
}
async forgotPassword(data: { type: ForgotPasswordType; input?: string; phoneCode?: string; validateCode: string; password: string; confirmPassword: string }) {
if (!data.type) {
throw new CommonException('找回类型不能为空');
throw new CommonException("找回类型不能为空");
}
if (data.password !== data.confirmPassword) {
throw new CommonException('两次输入的密码不一致');
throw new CommonException("两次输入的密码不一致");
}
const where: any = {
[data.type]: data.input,
};
if (data.type === 'mobile') {
where.phoneCode = data.phoneCode ?? '86';
if (data.type === "mobile") {
where.phoneCode = data.phoneCode ?? "86";
}
const user = await this.findOne({ [data.type]: data.input });
console.log('user', user);
console.log("user", user);
if (!user) {
throw new CommonException('用户不存在');
throw new CommonException("用户不存在");
// return;
}
await this.resetPassword(user.id, data.password);
@@ -284,7 +283,7 @@ export class UserService extends BaseService<UserEntity> {
const user = await this.info(userId);
const passwordChecked = await this.checkPassword(form.password, user.password, user.passwordVersion);
if (!passwordChecked) {
throw new CommonException('原密码错误');
throw new CommonException("原密码错误");
}
const param = {
id: userId,
@@ -294,9 +293,26 @@ export class UserService extends BaseService<UserEntity> {
await this.update(param);
}
async initPassword(userId: any, form: any) {
const user = await this.info(userId);
if (user.password !== "changeme") {
throw new CommonException("当前账号已设置密码");
}
if (!form.newPassword) {
throw new CommonException("新密码不能为空");
}
if (form.newPassword !== form.confirmNewPassword) {
throw new CommonException("两次输入的密码不一致");
}
await this.update({
id: userId,
password: form.newPassword,
});
}
async resetPassword(userId: any, newPasswd: string) {
if (!userId) {
throw new CommonException('userId不能为空');
throw new CommonException("userId不能为空");
}
const param = {
id: userId,
@@ -307,15 +323,15 @@ export class UserService extends BaseService<UserEntity> {
//@ts-ignore
async delete(ids: any) {
if (typeof ids === 'string') {
ids = ids.split(',');
if (typeof ids === "string") {
ids = ids.split(",");
ids = ids.map(id => parseInt(id));
}
if (ids.length === 0) {
return;
}
if (ids.includes(1)) {
throw new CommonException('不能删除管理员');
throw new CommonException("不能删除管理员");
}
await super.delete(ids);
await this.oauthBoundService.deleteWhere({
@@ -325,7 +341,7 @@ export class UserService extends BaseService<UserEntity> {
async isAdmin(userId: any) {
if (!userId) {
throw new CommonException('userId不能为空');
throw new CommonException("userId不能为空");
}
const userRoles = await this.userRoleService.find({
where: {
@@ -340,7 +356,7 @@ export class UserService extends BaseService<UserEntity> {
async updateStatus(id: number, status: number) {
if (!id) {
throw new CommonException('userId不能为空');
throw new CommonException("userId不能为空");
}
await this.repository.update(id, {
status,
@@ -357,16 +373,16 @@ export class UserService extends BaseService<UserEntity> {
}
async registerCountPerDay(param: { days: number } = { days: 7 }) {
const todayEnd = dayjs().endOf('day');
const todayEnd = dayjs().endOf("day");
const result = await this.getRepository()
.createQueryBuilder('main')
.select(`${this.dbAdapter.date('main.createTime')} AS date`) // 将UNIX时间戳转换为日期
.addSelect('COUNT(1) AS count')
.createQueryBuilder("main")
.select(`${this.dbAdapter.date("main.createTime")} AS date`) // 将UNIX时间戳转换为日期
.addSelect("COUNT(1) AS count")
.where({
// 0点
createTime: MoreThan(todayEnd.add(-param.days, 'day').toDate()),
createTime: MoreThan(todayEnd.add(-param.days, "day").toDate()),
})
.groupBy('date')
.groupBy("date")
.getRawMany();
return result;
@@ -386,7 +402,7 @@ export class UserService extends BaseService<UserEntity> {
status: 1,
},
order: {
updateTime: 'DESC',
updateTime: "DESC",
},
});
}
@@ -398,61 +414,61 @@ export class UserService extends BaseService<UserEntity> {
});
}
async verifyIdentity(userId: number, body: { identityType: 'password' | 'email' | 'mobile'; identityPassword?: string; identityValidateCode?: string }, codeService: any) {
async verifyIdentity(userId: number, body: { identityType: "password" | "email" | "mobile"; identityPassword?: string; identityValidateCode?: string }, codeService: any) {
const user = await this.info(userId);
if (body.identityType === 'password') {
if (body.identityType === "password") {
const passwordChecked = await this.checkPassword(body.identityPassword, user.password, user.passwordVersion);
if (!passwordChecked) {
throw new CommonException('密码错误');
throw new CommonException("密码错误");
}
return;
}
if (body.identityType === 'email') {
if (body.identityType === "email") {
if (!user.email) {
throw new CommonException('当前账号未绑定邮箱');
throw new CommonException("当前账号未绑定邮箱");
}
codeService.checkEmailCode({
email: user.email,
validateCode: body.identityValidateCode,
verificationType: 'contactIdentity',
verificationType: "contactIdentity",
throwError: true,
});
return;
}
if (body.identityType === 'mobile') {
if (body.identityType === "mobile") {
if (!user.mobile) {
throw new CommonException('当前账号未绑定手机号');
throw new CommonException("当前账号未绑定手机号");
}
await codeService.checkSmsCode({
mobile: user.mobile,
phoneCode: user.phoneCode || '86',
phoneCode: user.phoneCode || "86",
smsCode: body.identityValidateCode,
verificationType: 'contactIdentity',
verificationType: "contactIdentity",
throwError: true,
});
return;
}
throw new CommonException('不支持的验证方式');
throw new CommonException("不支持的验证方式");
}
checkContactIdentityValidation(userId: number, validationCode: string, codeService: any) {
const validationValue = codeService.getValidationValue(validationCode);
if (!validationValue || validationValue.type !== 'contactIdentity' || validationValue.userId !== userId) {
throw new CommonException('请先验证本人操作');
if (!validationValue || validationValue.type !== "contactIdentity" || validationValue.userId !== userId) {
throw new CommonException("请先验证本人操作");
}
}
async updateMobile(userId: number, body: { phoneCode?: string; mobile: string }) {
const mobile = body.mobile?.trim();
if (!mobile) {
throw new CommonException('手机号不能为空');
throw new CommonException("手机号不能为空");
}
const old = await this.findOne(buildUserContactConflictWhere(mobile, userId));
if (old != null) {
throw new CommonException('手机号已被占用');
throw new CommonException("手机号已被占用");
}
await this.repository.update(userId, {
phoneCode: body.phoneCode || '86',
phoneCode: body.phoneCode || "86",
mobile,
});
}
@@ -460,11 +476,11 @@ export class UserService extends BaseService<UserEntity> {
async updateEmail(userId: number, body: { email: string }) {
const email = body.email?.trim();
if (!email) {
throw new CommonException('邮箱不能为空');
throw new CommonException("邮箱不能为空");
}
const old = await this.findOne(buildUserContactConflictWhere(email, userId));
if (old != null) {
throw new CommonException('邮箱已被占用');
throw new CommonException("邮箱已被占用");
}
await this.repository.update(userId, {
email,
@@ -473,7 +489,7 @@ export class UserService extends BaseService<UserEntity> {
async getAllUserIds() {
const users = await this.repository.find({
select: ['id'],
select: ["id"],
where: {
status: 1,
},