|
@@ -1,8 +1,9 @@
|
|
|
-// oms/src/controllers/userController.ts
|
|
|
|
|
import { Request, Response } from "express";
|
|
import { Request, Response } from "express";
|
|
|
import userService from "../services/userService"; // 导入用户服务
|
|
import userService from "../services/userService"; // 导入用户服务
|
|
|
-import { v4 as uuidv4 } from "uuid"; // 用于生成唯一的 uid (虽然现在uid是必需的,但保留以备其他用途)
|
|
|
|
|
-import { IUser } from "../models/userModel"; // 导入 IUser 接口
|
|
|
|
|
|
|
+import { fcmService } from "../services/fcmService";
|
|
|
|
|
+import { v4 as uuidv4 } from "uuid";
|
|
|
|
|
+import { IUser } from "../models/userModel";
|
|
|
|
|
+import * as admin from "firebase-admin";
|
|
|
|
|
|
|
|
// Define an array of valid IUser keys for runtime checking
|
|
// Define an array of valid IUser keys for runtime checking
|
|
|
// This ensures that only properties defined in IUser are considered for MongoDB queries
|
|
// This ensures that only properties defined in IUser are considered for MongoDB queries
|
|
@@ -38,31 +39,24 @@ class UserController {
|
|
|
*/
|
|
*/
|
|
|
public async createUser(req: Request, res: Response): Promise<void> {
|
|
public async createUser(req: Request, res: Response): Promise<void> {
|
|
|
try {
|
|
try {
|
|
|
- const { uid, ...otherData } = req.body; // uid 现在是必需的
|
|
|
|
|
|
|
+ const { uid, ...otherData } = req.body; // uid 现在是必需的 // 检查 uid 是否存在
|
|
|
|
|
|
|
|
- // 检查 uid 是否存在
|
|
|
|
|
if (!uid) {
|
|
if (!uid) {
|
|
|
res.status(400).json({ message: "用户 UID 是必需的。" });
|
|
res.status(400).json({ message: "用户 UID 是必需的。" });
|
|
|
return;
|
|
return;
|
|
|
- }
|
|
|
|
|
|
|
+ } // 如果 uid 存在,则直接用 req.body 来创建用户
|
|
|
|
|
|
|
|
- // 如果 uid 存在,则直接用 req.body 来创建用户
|
|
|
|
|
const user = await userService.createUser(req.body);
|
|
const user = await userService.createUser(req.body);
|
|
|
res.status(201).json(user);
|
|
res.status(201).json(user);
|
|
|
} catch (error: any) {
|
|
} catch (error: any) {
|
|
|
// 检查是否是重复键错误 (uid 唯一性冲突)
|
|
// 检查是否是重复键错误 (uid 唯一性冲突)
|
|
|
- if (
|
|
|
|
|
- error.message.includes("E11000 duplicate key error") ||
|
|
|
|
|
- error.message.includes("duplicate key")
|
|
|
|
|
- ) {
|
|
|
|
|
|
|
+ if (error.message.includes("E11000 duplicate key error") || error.message.includes("duplicate key")) {
|
|
|
res.status(409).json({
|
|
res.status(409).json({
|
|
|
message: "用户 UID 已存在,请使用其他 UID。",
|
|
message: "用户 UID 已存在,请使用其他 UID。",
|
|
|
error: error.message,
|
|
error: error.message,
|
|
|
});
|
|
});
|
|
|
} else {
|
|
} else {
|
|
|
- res
|
|
|
|
|
- .status(500)
|
|
|
|
|
- .json({ message: "创建用户时出错", error: error.message });
|
|
|
|
|
|
|
+ res.status(500).json({ message: "创建用户时出错", error: error.message });
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
@@ -71,6 +65,7 @@ class UserController {
|
|
|
* 处理通过 UID 获取用户的请求。
|
|
* 处理通过 UID 获取用户的请求。
|
|
|
* GET /api/users/:uid
|
|
* GET /api/users/:uid
|
|
|
*/
|
|
*/
|
|
|
|
|
+
|
|
|
public async getUserByUid(req: Request, res: Response): Promise<void> {
|
|
public async getUserByUid(req: Request, res: Response): Promise<void> {
|
|
|
try {
|
|
try {
|
|
|
const { uid } = req.params;
|
|
const { uid } = req.params;
|
|
@@ -89,12 +84,12 @@ class UserController {
|
|
|
* 处理更新用户的请求。
|
|
* 处理更新用户的请求。
|
|
|
* PUT /api/users/:uid
|
|
* PUT /api/users/:uid
|
|
|
*/
|
|
*/
|
|
|
|
|
+
|
|
|
public async updateUser(req: Request, res: Response): Promise<void> {
|
|
public async updateUser(req: Request, res: Response): Promise<void> {
|
|
|
try {
|
|
try {
|
|
|
const { uid } = req.params;
|
|
const { uid } = req.params;
|
|
|
- const updateData = req.body;
|
|
|
|
|
|
|
+ const updateData = req.body; // 业务逻辑检查:不允许直接通过 PUT 请求更新 UID
|
|
|
|
|
|
|
|
- // 业务逻辑检查:不允许直接通过 PUT 请求更新 UID
|
|
|
|
|
if (updateData.uid && updateData.uid !== uid) {
|
|
if (updateData.uid && updateData.uid !== uid) {
|
|
|
res.status(400).json({ message: "不允许修改用户 UID。" });
|
|
res.status(400).json({ message: "不允许修改用户 UID。" });
|
|
|
return;
|
|
return;
|
|
@@ -111,11 +106,11 @@ class UserController {
|
|
|
res.status(500).json({ message: "更新用户时出错", error: error.message });
|
|
res.status(500).json({ message: "更新用户时出错", error: error.message });
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
-
|
|
|
|
|
/**
|
|
/**
|
|
|
* 处理删除用户的请求。
|
|
* 处理删除用户的请求。
|
|
|
* DELETE /api/users/:uid
|
|
* DELETE /api/users/:uid
|
|
|
*/
|
|
*/
|
|
|
|
|
+
|
|
|
public async deleteUser(req: Request, res: Response): Promise<void> {
|
|
public async deleteUser(req: Request, res: Response): Promise<void> {
|
|
|
try {
|
|
try {
|
|
|
const { uid } = req.params;
|
|
const { uid } = req.params;
|
|
@@ -129,31 +124,25 @@ class UserController {
|
|
|
res.status(500).json({ message: "删除用户时出错", error: error.message });
|
|
res.status(500).json({ message: "删除用户时出错", error: error.message });
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
-
|
|
|
|
|
/**
|
|
/**
|
|
|
* 处理获取所有用户或按分页和查询参数获取用户列表的请求。
|
|
* 处理获取所有用户或按分页和查询参数获取用户列表的请求。
|
|
|
* GET /api/users?page=1&limit=10&project=1&cc=US
|
|
* GET /api/users?page=1&limit=10&project=1&cc=US
|
|
|
*/
|
|
*/
|
|
|
|
|
+
|
|
|
public async getPaginatedUsers(req: Request, res: Response): Promise<void> {
|
|
public async getPaginatedUsers(req: Request, res: Response): Promise<void> {
|
|
|
try {
|
|
try {
|
|
|
const page = parseInt(req.query.page as string) || 1;
|
|
const page = parseInt(req.query.page as string) || 1;
|
|
|
- const limit = parseInt(req.query.limit as string) || 30;
|
|
|
|
|
|
|
+ const limit = parseInt(req.query.limit as string) || 30; // 从 req.query 中筛选出只与 IUser 相关的属性,作为 MongoDB 的查询条件
|
|
|
|
|
|
|
|
- // 从 req.query 中筛选出只与 IUser 相关的属性,作为 MongoDB 的查询条件
|
|
|
|
|
const mongooseQuery: Partial<IUser> = {};
|
|
const mongooseQuery: Partial<IUser> = {};
|
|
|
for (const key in req.query) {
|
|
for (const key in req.query) {
|
|
|
- if (req.query.hasOwnProperty(key)) {
|
|
|
|
|
|
|
+ if (Object.prototype.hasOwnProperty.call(req.query, key)) {
|
|
|
// 检查 key 是否在 USER_MODEL_KEYS 中 (即是否是 IUser 的有效属性)
|
|
// 检查 key 是否在 USER_MODEL_KEYS 中 (即是否是 IUser 的有效属性)
|
|
|
// 并且不是分页参数 'page' 或 'limit'
|
|
// 并且不是分页参数 'page' 或 'limit'
|
|
|
if (USER_MODEL_KEYS.includes(key as keyof IUser)) {
|
|
if (USER_MODEL_KEYS.includes(key as keyof IUser)) {
|
|
|
- const queryValue = req.query[key];
|
|
|
|
|
-
|
|
|
|
|
- // 根据需要进行类型转换
|
|
|
|
|
- if (
|
|
|
|
|
- key === "project" ||
|
|
|
|
|
- key === "apiLevel" ||
|
|
|
|
|
- key === "versionCode"
|
|
|
|
|
- ) {
|
|
|
|
|
|
|
+ const queryValue = req.query[key]; // 根据需要进行类型转换
|
|
|
|
|
+
|
|
|
|
|
+ if (key === "project" || key === "apiLevel" || key === "versionCode") {
|
|
|
const numValue = parseInt(queryValue as string);
|
|
const numValue = parseInt(queryValue as string);
|
|
|
if (!isNaN(numValue)) {
|
|
if (!isNaN(numValue)) {
|
|
|
mongooseQuery[key as keyof IUser] = numValue as any;
|
|
mongooseQuery[key as keyof IUser] = numValue as any;
|
|
@@ -165,9 +154,7 @@ class UserController {
|
|
|
}
|
|
}
|
|
|
} else if (key === "tags") {
|
|
} else if (key === "tags") {
|
|
|
// 如果 tags 是以逗号分隔的字符串,可以将其转换为数组
|
|
// 如果 tags 是以逗号分隔的字符串,可以将其转换为数组
|
|
|
- mongooseQuery[key as keyof IUser] = (queryValue as string)
|
|
|
|
|
- .split(",")
|
|
|
|
|
- .map((s) => s.trim()) as any;
|
|
|
|
|
|
|
+ mongooseQuery[key as keyof IUser] = (queryValue as string).split(",").map((s) => s.trim()) as any;
|
|
|
} else if (key === "firstLoginAt" || key === "lastActiveAt") {
|
|
} else if (key === "firstLoginAt" || key === "lastActiveAt") {
|
|
|
// 尝试将字符串转换为 Date 对象
|
|
// 尝试将字符串转换为 Date 对象
|
|
|
try {
|
|
try {
|
|
@@ -177,13 +164,9 @@ class UserController {
|
|
|
mongooseQuery[key as keyof IUser] = dateValue as any;
|
|
mongooseQuery[key as keyof IUser] = dateValue as any;
|
|
|
}
|
|
}
|
|
|
} catch (e) {
|
|
} catch (e) {
|
|
|
- console.warn(
|
|
|
|
|
- `Invalid date format for ${key}: ${queryValue}. Skipping.`
|
|
|
|
|
- );
|
|
|
|
|
- // 可以选择在这里返回错误或忽略该查询参数
|
|
|
|
|
|
|
+ console.warn(`Invalid date format for ${key}: ${queryValue}. Skipping.`); // 可以选择在这里返回错误或忽略该查询参数
|
|
|
}
|
|
}
|
|
|
- }
|
|
|
|
|
- // 对于其他字符串类型,直接赋值
|
|
|
|
|
|
|
+ } // 对于其他字符串类型,直接赋值
|
|
|
else {
|
|
else {
|
|
|
mongooseQuery[key as keyof IUser] = queryValue as any;
|
|
mongooseQuery[key as keyof IUser] = queryValue as any;
|
|
|
}
|
|
}
|
|
@@ -204,9 +187,62 @@ class UserController {
|
|
|
users,
|
|
users,
|
|
|
});
|
|
});
|
|
|
} catch (error: any) {
|
|
} catch (error: any) {
|
|
|
- res
|
|
|
|
|
- .status(500)
|
|
|
|
|
- .json({ message: "获取用户列表时出错", error: error.message });
|
|
|
|
|
|
|
+ res.status(500).json({ message: "获取用户列表时出错", error: error.message });
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 处理用户任务完成并发送 FCM 通知。
|
|
|
|
|
+ * POST /api/users/task-complete
|
|
|
|
|
+ * @param req 请求体应包含用户的 UID 和任务 ID。
|
|
|
|
|
+ */
|
|
|
|
|
+ public async handleTaskCompletion(req: Request, res: Response): Promise<void> {
|
|
|
|
|
+ const { uid, taskId } = req.body; // 1. 验证请求参数
|
|
|
|
|
+
|
|
|
|
|
+ if (!uid || !taskId) {
|
|
|
|
|
+ res.status(400).json({ message: "uid 和 taskId 是必需的。" });
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ try {
|
|
|
|
|
+ // 2. 从数据库中查找用户的 FCM Token
|
|
|
|
|
+ const user = await userService.getUserByUid(uid);
|
|
|
|
|
+ if (!user || !user.fmToken) {
|
|
|
|
|
+ console.warn(`[FCM] 用户 ${uid} 没有 FCM token。不发送通知。`);
|
|
|
|
|
+ res.status(200).json({
|
|
|
|
|
+ message: "任务处理成功,但未发送通知。",
|
|
|
|
|
+ });
|
|
|
|
|
+ return;
|
|
|
|
|
+ } // 3. 构建 FCM 消息载荷
|
|
|
|
|
+
|
|
|
|
|
+ const message: admin.messaging.Message = {
|
|
|
|
|
+ notification: {
|
|
|
|
|
+ title: "任务完成!",
|
|
|
|
|
+ body: "恭喜您完成了一个新任务!",
|
|
|
|
|
+ },
|
|
|
|
|
+ data: {
|
|
|
|
|
+ event: "task_completed",
|
|
|
|
|
+ taskId: taskId,
|
|
|
|
|
+ },
|
|
|
|
|
+ token: user.fmToken, // 添加 token 属性,使其成为 TokenMessage
|
|
|
|
|
+ }; // 4. 调用 fcmService 发送通知并检查返回值
|
|
|
|
|
+
|
|
|
|
|
+ const response = await fcmService.sendNotificationToUser(uid, user.fmToken, message); // 5. 如果返回值为 undefined,说明发送失败(通常是 token 无效),进行相应处理
|
|
|
|
|
+
|
|
|
|
|
+ if (!response) {
|
|
|
|
|
+ // fcmService 已经处理了日志记录,但我们也可以在这里执行其他逻辑,
|
|
|
|
|
+ // 例如:在数据库中清理无效的 token。
|
|
|
|
|
+ console.warn(`[FCM] 发送通知失败。正在从用户 ${uid} 的数据库中清理无效的 token...`);
|
|
|
|
|
+ await userService.updateUser(uid, { fmToken: undefined });
|
|
|
|
|
+ res.status(200).json({ message: "任务处理成功,但通知发送失败(无效 Token)。" });
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ res.status(200).json({ message: "任务处理成功,通知已发送。" });
|
|
|
|
|
+ } catch (error: any) {
|
|
|
|
|
+ // 这里的 catch 只会捕获 userService 或其他可能抛出的异常,而不是 FCM 发送失败的异常
|
|
|
|
|
+ console.error(`[FCM] 处理任务完成时出错:`, error);
|
|
|
|
|
+ res.status(500).json({ message: "处理任务完成时出错。", error: error.message });
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|