member.controller.ts 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519
  1. import { FastifyRequest, FastifyReply, FastifyInstance } from 'fastify'
  2. import { MemberService } from '../services/member.service'
  3. import {
  4. CreateMemberBody,
  5. UpdateMemberBody,
  6. ListMemberQuery,
  7. MemberResponse,
  8. UpdateGuestBody,
  9. MemberLoginBody,
  10. ResetPasswordBody,
  11. RegisterBody,
  12. UpdateProfileBody
  13. } from '../dto/member.dto'
  14. import { VipLevel, MemberStatus } from '../entities/member.entity'
  15. import { UserService } from '../services/user.service'
  16. export class MemberController {
  17. private memberService: MemberService
  18. private userService: UserService
  19. constructor(app: FastifyInstance) {
  20. this.memberService = new MemberService(app)
  21. this.userService = new UserService(app)
  22. }
  23. async createGuest(request: FastifyRequest<{ Querystring: { code?: string; ref?: string } }>, reply: FastifyReply) {
  24. try {
  25. const { code, ref } = request.query || {}
  26. const ip =
  27. request.ip ||
  28. (request.headers['x-forwarded-for'] as string) ||
  29. (request.headers['x-real-ip'] as string) ||
  30. 'unknown'
  31. let domain = undefined
  32. if (ref) {
  33. domain = ref
  34. } else {
  35. domain = request.headers.origin
  36. }
  37. const user = await this.memberService.createGuest(code, domain, ip)
  38. const token = await reply.jwtSign({ id: user.id, name: user.name, role: user.role })
  39. return reply.code(201).send({
  40. user: {
  41. id: user.id,
  42. name: user.name,
  43. vipLevel: VipLevel.GUEST
  44. },
  45. token
  46. })
  47. } catch (error) {
  48. return reply.code(500).send({ message: '创建游客失败' })
  49. }
  50. }
  51. async upgradeGuest(request: FastifyRequest<{ Body: UpdateGuestBody }>, reply: FastifyReply) {
  52. try {
  53. const { userId, name, password, email, phone } = request.body
  54. if (!name || !password) {
  55. return reply.code(400).send({ message: '用户名和密码为必填字段' })
  56. }
  57. if (name.length < 3 || name.length > 20) {
  58. return reply.code(400).send({ message: '用户名长度必须在3-20个字符之间' })
  59. }
  60. if (password.length < 6) {
  61. return reply.code(400).send({ message: '密码长度不能少于6个字符' })
  62. }
  63. if (email && !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
  64. return reply.code(400).send({ message: '邮箱格式不正确' })
  65. }
  66. if (phone && !/^1[3-9]\d{9}$/.test(phone)) {
  67. return reply.code(400).send({ message: '手机号格式不正确' })
  68. }
  69. await this.memberService.upgradeGuest(userId, name, password, email, phone)
  70. return reply.send({ message: '游客账户转换成功' })
  71. } catch (error) {
  72. const errorMessage = error instanceof Error ? error.message : '更新游客失败'
  73. if (errorMessage.includes('不存在')) {
  74. return reply.code(404).send({ message: errorMessage })
  75. } else if (errorMessage.includes('已被使用') || errorMessage.includes('格式')) {
  76. return reply.code(400).send({ message: errorMessage })
  77. }
  78. return reply.code(500).send({ message: '更新游客失败' })
  79. }
  80. }
  81. async register(request: FastifyRequest<{ Body: RegisterBody }>, reply: FastifyReply) {
  82. try {
  83. const { name, password, email, phone, code } = request.body
  84. // 验证必填字段
  85. if (!name || !password) {
  86. return reply.code(400).send({ message: '用户名和密码为必填字段' })
  87. }
  88. // 验证用户名格式
  89. if (name.length < 3 || name.length > 20) {
  90. return reply.code(400).send({ message: '用户名长度必须在3-20个字符之间' })
  91. }
  92. // 验证密码格式
  93. if (password.length < 6) {
  94. return reply.code(400).send({ message: '密码长度不能少于6个字符' })
  95. }
  96. // 验证邮箱格式
  97. if (email && !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
  98. return reply.code(400).send({ message: '邮箱格式不正确' })
  99. }
  100. // 验证手机号格式
  101. if (phone && !/^1[3-9]\d{9}$/.test(phone)) {
  102. return reply.code(400).send({ message: '手机号格式不正确' })
  103. }
  104. // 获取客户端IP
  105. const ip =
  106. request.ip ||
  107. (request.headers['x-forwarded-for'] as string) ||
  108. (request.headers['x-real-ip'] as string) ||
  109. 'unknown'
  110. // 调用注册服务
  111. const { user, member } = await this.memberService.register(name, password, email, phone, code, ip)
  112. // 生成JWT token
  113. const token = await reply.jwtSign({ id: user.id, name: user.name, role: user.role })
  114. return reply.code(201).send({
  115. message: '注册成功',
  116. user: {
  117. id: user.id,
  118. name: user.name,
  119. role: user.role,
  120. vipLevel: member.vipLevel
  121. },
  122. token
  123. })
  124. } catch (error) {
  125. const errorMessage = error instanceof Error ? error.message : '注册失败'
  126. if (errorMessage.includes('已被使用')) {
  127. return reply.code(400).send({ message: errorMessage })
  128. }
  129. return reply.code(500).send({ message: '注册失败' })
  130. }
  131. }
  132. async memberLogin(request: FastifyRequest<{ Body: MemberLoginBody }>, reply: FastifyReply) {
  133. try {
  134. const { name, password } = request.body
  135. if (!name || !password) {
  136. return reply.code(400).send({ message: '请输入用户名和密码' })
  137. }
  138. const loginResult = await this.memberService.validateMemberLogin(name, password)
  139. if (!loginResult) {
  140. return reply.code(401).send({ message: '用户名或密码错误' })
  141. }
  142. const { user, member } = loginResult
  143. const token = await reply.jwtSign({ id: user.id, name: user.name, role: user.role })
  144. await this.memberService.checkVipExpireTime(member)
  145. return reply.send({
  146. user: {
  147. id: user.id,
  148. name: user.name,
  149. role: user.role,
  150. vipLevel: member.vipLevel
  151. },
  152. token
  153. })
  154. } catch (error) {
  155. return reply.code(500).send({ message: '登录失败' })
  156. }
  157. }
  158. async profile(request: FastifyRequest, reply: FastifyReply) {
  159. try {
  160. const user = await this.userService.findById(request.user.id)
  161. if (!user) {
  162. return reply.code(404).send({ message: '会员信息不存在' })
  163. }
  164. const member = await this.memberService.findByUserId(user.id)
  165. if (!member) {
  166. return reply.code(404).send({ message: '会员信息不存在' })
  167. }
  168. await this.memberService.checkVipExpireTime(member)
  169. return reply.send({
  170. id: user.id,
  171. name: user.name,
  172. role: user.role,
  173. vipLevel: member.vipLevel,
  174. vipExpireTime: member.vipExpireTime
  175. })
  176. } catch (error) {
  177. return reply.code(500).send({ message: '获取会员信息失败' })
  178. }
  179. }
  180. async resetPassword(request: FastifyRequest<{ Body: ResetPasswordBody }>, reply: FastifyReply) {
  181. try {
  182. const { password } = request.body
  183. if (password.length < 6) {
  184. return reply.code(400).send({ message: '密码长度必须至少8位' })
  185. }
  186. await this.userService.resetPassword(request.user.id, password)
  187. return reply.send({ message: '密码重置成功' })
  188. } catch (error) {
  189. return reply.code(500).send({ message: '重置密码失败' })
  190. }
  191. }
  192. async updateProfile(request: FastifyRequest<{ Body: UpdateProfileBody }>, reply: FastifyReply) {
  193. try {
  194. const { name, email } = request.body
  195. // 验证必填字段
  196. if (!name) {
  197. return reply.code(400).send({ message: '用户名为必填字段' })
  198. }
  199. // 验证用户名格式
  200. if (name.length < 3 || name.length > 20) {
  201. return reply.code(400).send({ message: '用户名长度必须在3-20个字符之间' })
  202. }
  203. // 验证邮箱格式
  204. if (email && !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
  205. return reply.code(400).send({ message: '邮箱格式不正确' })
  206. }
  207. await this.memberService.updateProfile(request.user.id, name, email)
  208. return reply.send({ message: '个人信息更新成功' })
  209. } catch (error) {
  210. const errorMessage = error instanceof Error ? error.message : '更新个人信息失败'
  211. if (errorMessage.includes('已被使用')) {
  212. return reply.code(400).send({ message: errorMessage })
  213. }
  214. return reply.code(500).send({ message: '更新个人信息失败' })
  215. }
  216. }
  217. async updateVipLevel(
  218. request: FastifyRequest<{
  219. Params: { id: string }
  220. Body: { vipLevel: VipLevel; vipExpireTime?: Date }
  221. }>,
  222. reply: FastifyReply
  223. ) {
  224. try {
  225. const id = parseInt(request.params.id)
  226. const { vipLevel, vipExpireTime } = request.body
  227. const updatedMember = await this.memberService.updateVipLevel(id, vipLevel, vipExpireTime)
  228. return reply.send({
  229. member: {
  230. vipLevel: updatedMember.vipLevel,
  231. vipExpireTime: updatedMember.vipExpireTime
  232. }
  233. })
  234. } catch (error) {
  235. return reply.code(500).send({ message: '更新VIP等级失败' })
  236. }
  237. }
  238. async create(request: FastifyRequest<{ Body: CreateMemberBody }>, reply: FastifyReply) {
  239. try {
  240. const { userId, email, phone, vipLevel, status, vipExpireTime } = request.body
  241. // 检查用户是否已存在
  242. const existingMember = await this.memberService.findByUserId(userId)
  243. if (existingMember) {
  244. return reply.code(400).send({ message: '该用户已经是会员' })
  245. }
  246. // 检查邮箱是否已存在
  247. if (email) {
  248. const existingEmail = await this.memberService.findByEmail(email)
  249. if (existingEmail) {
  250. return reply.code(400).send({ message: '邮箱已被使用' })
  251. }
  252. }
  253. // 检查手机号是否已存在
  254. if (phone) {
  255. const existingPhone = await this.memberService.findByPhone(phone)
  256. if (existingPhone) {
  257. return reply.code(400).send({ message: '手机号已被使用' })
  258. }
  259. }
  260. const member = await this.memberService.create({
  261. userId,
  262. email,
  263. phone,
  264. vipLevel,
  265. status,
  266. vipExpireTime
  267. })
  268. return reply.code(201).send({ message: '创建会员成功' })
  269. } catch (error) {
  270. return reply.code(500).send({ message: '创建会员失败' })
  271. }
  272. }
  273. async getById(request: FastifyRequest<{ Params: { id: string } }>, reply: FastifyReply) {
  274. try {
  275. const id = parseInt(request.params.id)
  276. const member = await this.memberService.findById(id)
  277. return reply.send({
  278. member: {
  279. id: member.id,
  280. userId: member.userId,
  281. email: member.email,
  282. phone: member.phone,
  283. vipLevel: member.vipLevel,
  284. status: member.status,
  285. vipExpireTime: member.vipExpireTime,
  286. lastLoginAt: member.lastLoginAt,
  287. createdAt: member.createdAt,
  288. updatedAt: member.updatedAt
  289. }
  290. })
  291. } catch (error) {
  292. return reply.code(404).send({ message: '会员不存在' })
  293. }
  294. }
  295. async getByUserId(request: FastifyRequest<{ Params: { userId: string } }>, reply: FastifyReply) {
  296. try {
  297. const userId = parseInt(request.params.userId)
  298. const member = await this.memberService.findByUserId(userId)
  299. if (!member) {
  300. return reply.code(404).send({ message: '会员不存在' })
  301. }
  302. return reply.send({
  303. member: {
  304. id: member.id,
  305. userId: member.userId,
  306. email: member.email,
  307. phone: member.phone,
  308. vipLevel: member.vipLevel,
  309. status: member.status,
  310. vipExpireTime: member.vipExpireTime,
  311. lastLoginAt: member.lastLoginAt,
  312. createdAt: member.createdAt,
  313. updatedAt: member.updatedAt
  314. }
  315. })
  316. } catch (error) {
  317. return reply.code(500).send({ message: '查询会员失败' })
  318. }
  319. }
  320. async list(request: FastifyRequest<{ Querystring: ListMemberQuery }>, reply: FastifyReply) {
  321. try {
  322. const { page, size, vipLevel, status, userId } = request.query
  323. const result = await this.memberService.list(page || 0, size || 20, { vipLevel, status, userId })
  324. return reply.send(result)
  325. } catch (error) {
  326. return reply.code(500).send({ message: '查询会员列表失败' })
  327. }
  328. }
  329. async update(request: FastifyRequest<{ Body: UpdateMemberBody }>, reply: FastifyReply) {
  330. try {
  331. const { id, userId, email, phone, vipLevel, status, vipExpireTime } = request.body
  332. // 检查会员是否存在
  333. try {
  334. await this.memberService.findById(id)
  335. } catch (error) {
  336. return reply.code(404).send({ message: '会员不存在' })
  337. }
  338. // 检查邮箱是否已被其他会员使用
  339. if (email) {
  340. const existingEmail = await this.memberService.findByEmail(email)
  341. if (existingEmail && existingEmail.id !== id) {
  342. return reply.code(400).send({ message: '邮箱已被其他会员使用' })
  343. }
  344. }
  345. // 检查手机号是否已被其他会员使用
  346. if (phone) {
  347. const existingPhone = await this.memberService.findByPhone(phone)
  348. if (existingPhone && existingPhone.id !== id) {
  349. return reply.code(400).send({ message: '手机号已被其他会员使用' })
  350. }
  351. }
  352. const updatedMember = await this.memberService.update(id, {
  353. userId,
  354. email,
  355. phone,
  356. vipLevel,
  357. status,
  358. vipExpireTime
  359. })
  360. return reply.send({
  361. member: {
  362. id: updatedMember.id,
  363. userId: updatedMember.userId,
  364. email: updatedMember.email,
  365. phone: updatedMember.phone,
  366. vipLevel: updatedMember.vipLevel,
  367. status: updatedMember.status,
  368. vipExpireTime: updatedMember.vipExpireTime,
  369. lastLoginAt: updatedMember.lastLoginAt,
  370. createdAt: updatedMember.createdAt,
  371. updatedAt: updatedMember.updatedAt
  372. }
  373. })
  374. } catch (error) {
  375. return reply.code(500).send({ message: '更新会员失败' })
  376. }
  377. }
  378. async delete(request: FastifyRequest<{ Params: { id: string } }>, reply: FastifyReply) {
  379. try {
  380. const id = parseInt(request.params.id)
  381. // 检查会员是否存在
  382. try {
  383. await this.memberService.findById(id)
  384. } catch (error) {
  385. return reply.code(404).send({ message: '会员不存在' })
  386. }
  387. await this.memberService.delete(id)
  388. return reply.send({ message: '删除会员成功' })
  389. } catch (error) {
  390. return reply.code(500).send({ message: '删除会员失败' })
  391. }
  392. }
  393. async getAllMembers(request: FastifyRequest, reply: FastifyReply) {
  394. try {
  395. const members = await this.memberService.findAllMembers()
  396. return reply.send({ members })
  397. } catch (error) {
  398. return reply.code(500).send({ message: '获取所有会员失败' })
  399. }
  400. }
  401. async updateStatus(
  402. request: FastifyRequest<{
  403. Params: { id: string }
  404. Body: { status: MemberStatus }
  405. }>,
  406. reply: FastifyReply
  407. ) {
  408. try {
  409. const id = parseInt(request.params.id)
  410. const { status } = request.body
  411. const updatedMember = await this.memberService.updateStatus(id, status)
  412. return reply.send({
  413. member: {
  414. id: updatedMember.id,
  415. userId: updatedMember.userId,
  416. email: updatedMember.email,
  417. phone: updatedMember.phone,
  418. vipLevel: updatedMember.vipLevel,
  419. status: updatedMember.status,
  420. vipExpireTime: updatedMember.vipExpireTime,
  421. lastLoginAt: updatedMember.lastLoginAt,
  422. createdAt: updatedMember.createdAt,
  423. updatedAt: updatedMember.updatedAt
  424. }
  425. })
  426. } catch (error) {
  427. return reply.code(500).send({ message: '更新会员状态失败' })
  428. }
  429. }
  430. async updateLastLogin(request: FastifyRequest<{ Params: { id: string } }>, reply: FastifyReply) {
  431. try {
  432. const id = parseInt(request.params.id)
  433. await this.memberService.updateLastLogin(id)
  434. return reply.send({ message: '更新最后登录时间成功' })
  435. } catch (error) {
  436. return reply.code(500).send({ message: '更新最后登录时间失败' })
  437. }
  438. }
  439. async getStatistics(request: FastifyRequest, reply: FastifyReply) {
  440. try {
  441. const vipLevelStats = await this.memberService.countByVipLevel()
  442. const statusStats = await this.memberService.countByStatus()
  443. return reply.send({
  444. vipLevelStats,
  445. statusStats
  446. })
  447. } catch (error) {
  448. return reply.code(500).send({ message: '获取统计数据失败' })
  449. }
  450. }
  451. }