@svton/cli 1.2.1 → 1.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (102) hide show
  1. package/dist/index.js +13 -6
  2. package/dist/index.mjs +13 -6
  3. package/package.json +3 -1
  4. package/templates/apps/admin/next-env.d.ts +2 -0
  5. package/templates/apps/admin/next.config.js +15 -0
  6. package/templates/apps/admin/package.json.tpl +54 -0
  7. package/templates/apps/admin/postcss.config.js +6 -0
  8. package/templates/apps/admin/src/app/globals.css +37 -0
  9. package/templates/apps/admin/src/app/layout.tsx +19 -0
  10. package/templates/apps/admin/src/app/login/page.tsx +96 -0
  11. package/templates/apps/admin/src/app/page.tsx +8 -0
  12. package/templates/apps/admin/src/app/users/page.tsx +165 -0
  13. package/templates/apps/admin/src/components/ui/switch.tsx +29 -0
  14. package/templates/apps/admin/src/hooks/useAPI.ts +130 -0
  15. package/templates/apps/admin/src/lib/api-client.ts +112 -0
  16. package/templates/apps/admin/src/lib/api-server.ts +95 -0
  17. package/templates/apps/admin/tailwind.config.js +54 -0
  18. package/templates/apps/admin/tsconfig.json +22 -0
  19. package/templates/apps/backend/.env.example +29 -0
  20. package/templates/apps/backend/nest-cli.json +8 -0
  21. package/templates/apps/backend/package.json.tpl +57 -0
  22. package/templates/apps/backend/prisma/schema.prisma +72 -0
  23. package/templates/apps/backend/prisma/seed.ts +32 -0
  24. package/templates/apps/backend/src/app.controller.ts +15 -0
  25. package/templates/apps/backend/src/app.module.ts +85 -0
  26. package/templates/apps/backend/src/app.service.ts +12 -0
  27. package/templates/apps/backend/src/auth/auth.controller.ts +31 -0
  28. package/templates/apps/backend/src/auth/auth.module.ts +27 -0
  29. package/templates/apps/backend/src/auth/auth.service.ts +89 -0
  30. package/templates/apps/backend/src/auth/jwt-auth.guard.ts +5 -0
  31. package/templates/apps/backend/src/auth/jwt.strategy.ts +27 -0
  32. package/templates/apps/backend/src/config/env.schema.ts +35 -0
  33. package/templates/apps/backend/src/main.ts +51 -0
  34. package/templates/apps/backend/src/object-storage/object-storage.controller.ts +114 -0
  35. package/templates/apps/backend/src/object-storage/object-storage.module.ts +7 -0
  36. package/templates/apps/backend/src/prisma/prisma.module.ts +9 -0
  37. package/templates/apps/backend/src/prisma/prisma.service.ts +13 -0
  38. package/templates/apps/backend/src/user/user.controller.ts +50 -0
  39. package/templates/apps/backend/src/user/user.module.ts +12 -0
  40. package/templates/apps/backend/src/user/user.service.ts +117 -0
  41. package/templates/apps/backend/tsconfig.json +23 -0
  42. package/templates/apps/mobile/babel.config.js +8 -0
  43. package/templates/apps/mobile/config/index.ts +65 -0
  44. package/templates/apps/mobile/package.json.tpl +48 -0
  45. package/templates/apps/mobile/project.config.json.tpl +17 -0
  46. package/templates/apps/mobile/src/app.config.ts +9 -0
  47. package/templates/apps/mobile/src/app.scss +4 -0
  48. package/templates/apps/mobile/src/app.ts +8 -0
  49. package/templates/apps/mobile/src/hooks/useAPI.ts +285 -0
  50. package/templates/apps/mobile/src/pages/index/index.scss +7 -0
  51. package/templates/apps/mobile/src/pages/index/index.tsx +49 -0
  52. package/templates/apps/mobile/src/services/api.ts +155 -0
  53. package/templates/apps/mobile/src/services/upload.service.ts +41 -0
  54. package/templates/apps/mobile/tsconfig.json +21 -0
  55. package/templates/configs/authz.config.ts +10 -0
  56. package/templates/configs/cache.config.ts +14 -0
  57. package/templates/configs/oauth.config.ts +20 -0
  58. package/templates/configs/payment.config.ts +44 -0
  59. package/templates/configs/queue.config.ts +21 -0
  60. package/templates/configs/rate-limit.config.ts +16 -0
  61. package/templates/configs/sms.config.ts +11 -0
  62. package/templates/configs/storage.config.ts +14 -0
  63. package/templates/examples/README.md +258 -0
  64. package/templates/examples/authz/README.md +273 -0
  65. package/templates/examples/authz/roles.guard.ts +37 -0
  66. package/templates/examples/authz/user.controller.ts +116 -0
  67. package/templates/examples/cache/README.md +82 -0
  68. package/templates/examples/cache/user.controller.ts +42 -0
  69. package/templates/examples/cache/user.service.ts +78 -0
  70. package/templates/examples/oauth/README.md +192 -0
  71. package/templates/examples/oauth/auth.controller.ts +99 -0
  72. package/templates/examples/oauth/auth.service.ts +97 -0
  73. package/templates/examples/payment/README.md +151 -0
  74. package/templates/examples/payment/order.controller.ts +56 -0
  75. package/templates/examples/payment/order.service.ts +132 -0
  76. package/templates/examples/payment/webhook.controller.ts +73 -0
  77. package/templates/examples/queue/README.md +134 -0
  78. package/templates/examples/queue/email.controller.ts +34 -0
  79. package/templates/examples/queue/email.processor.ts +68 -0
  80. package/templates/examples/queue/email.service.ts +64 -0
  81. package/templates/examples/rate-limit/README.md +249 -0
  82. package/templates/examples/rate-limit/api.controller.ts +113 -0
  83. package/templates/examples/sms/README.md +121 -0
  84. package/templates/examples/sms/sms.service.ts +69 -0
  85. package/templates/examples/sms/verification.controller.ts +100 -0
  86. package/templates/examples/storage/README.md +224 -0
  87. package/templates/examples/storage/upload.controller.ts +117 -0
  88. package/templates/examples/storage/upload.service.ts +123 -0
  89. package/templates/packages/types/package.json.tpl +16 -0
  90. package/templates/packages/types/src/api.ts +88 -0
  91. package/templates/packages/types/src/common.ts +89 -0
  92. package/templates/packages/types/src/index.ts +3 -0
  93. package/templates/packages/types/tsconfig.json +16 -0
  94. package/templates/skills/authz.skill.md +42 -0
  95. package/templates/skills/base.skill.md +57 -0
  96. package/templates/skills/cache.skill.md +88 -0
  97. package/templates/skills/oauth.skill.md +41 -0
  98. package/templates/skills/payment.skill.md +129 -0
  99. package/templates/skills/queue.skill.md +140 -0
  100. package/templates/skills/rate-limit.skill.md +38 -0
  101. package/templates/skills/sms.skill.md +39 -0
  102. package/templates/skills/storage.skill.md +42 -0
@@ -0,0 +1,116 @@
1
+ import {
2
+ Controller,
3
+ Get,
4
+ Post,
5
+ Put,
6
+ Delete,
7
+ Body,
8
+ Param,
9
+ UseGuards,
10
+ } from '@nestjs/common';
11
+ import { Roles, Permissions, RolesGuard } from '@svton/nestjs-authz';
12
+
13
+ @Controller('examples/users')
14
+ @UseGuards(RolesGuard)
15
+ export class UserController {
16
+ /**
17
+ * 查看用户列表 - 需要 admin 或 manager 角色
18
+ */
19
+ @Get()
20
+ @Roles('admin', 'manager')
21
+ findAll() {
22
+ return {
23
+ message: 'User list',
24
+ users: [
25
+ { id: 1, name: 'User 1', role: 'admin' },
26
+ { id: 2, name: 'User 2', role: 'user' },
27
+ ],
28
+ };
29
+ }
30
+
31
+ /**
32
+ * 查看用户详情 - 所有登录用户都可以
33
+ */
34
+ @Get(':id')
35
+ findOne(@Param('id') id: string) {
36
+ return {
37
+ message: 'User detail',
38
+ user: { id, name: `User ${id}`, role: 'user' },
39
+ };
40
+ }
41
+
42
+ /**
43
+ * 创建用户 - 需要 admin 角色
44
+ */
45
+ @Post()
46
+ @Roles('admin')
47
+ create(@Body() data: any) {
48
+ return {
49
+ message: 'User created',
50
+ user: { id: 3, ...data },
51
+ };
52
+ }
53
+
54
+ /**
55
+ * 更新用户 - 需要 user:update 权限
56
+ */
57
+ @Put(':id')
58
+ @Permissions('user:update')
59
+ update(@Param('id') id: string, @Body() data: any) {
60
+ return {
61
+ message: 'User updated',
62
+ user: { id, ...data },
63
+ };
64
+ }
65
+
66
+ /**
67
+ * 删除用户 - 需要 admin 角色和 user:delete 权限
68
+ */
69
+ @Delete(':id')
70
+ @Roles('admin')
71
+ @Permissions('user:delete')
72
+ delete(@Param('id') id: string) {
73
+ return {
74
+ message: 'User deleted',
75
+ userId: id,
76
+ };
77
+ }
78
+
79
+ /**
80
+ * 批量删除 - 需要 admin 角色和 user:batch-delete 权限
81
+ */
82
+ @Delete()
83
+ @Roles('admin')
84
+ @Permissions('user:batch-delete')
85
+ batchDelete(@Body() data: { ids: string[] }) {
86
+ return {
87
+ message: 'Users deleted',
88
+ count: data.ids.length,
89
+ };
90
+ }
91
+
92
+ /**
93
+ * 导出用户 - 需要 admin 或 manager 角色
94
+ */
95
+ @Get('export')
96
+ @Roles('admin', 'manager')
97
+ export() {
98
+ return {
99
+ message: 'Export users',
100
+ url: 'https://example.com/users.xlsx',
101
+ };
102
+ }
103
+
104
+ /**
105
+ * 重置密码 - 需要 user:reset-password 权限
106
+ */
107
+ @Post(':id/reset-password')
108
+ @Permissions('user:reset-password')
109
+ resetPassword(@Param('id') id: string) {
110
+ return {
111
+ message: 'Password reset',
112
+ userId: id,
113
+ newPassword: 'temp123456',
114
+ };
115
+ }
116
+ }
@@ -0,0 +1,82 @@
1
+ # 缓存功能示例
2
+
3
+ 本示例展示如何使用 `@svton/nestjs-cache` 模块进行声明式缓存。
4
+
5
+ ## 文件说明
6
+
7
+ - `user.service.ts` - 使用缓存装饰器的 Service 层
8
+ - `user.controller.ts` - 对应的 Controller 层
9
+
10
+ ## 核心装饰器
11
+
12
+ ### @Cacheable - 缓存查询结果
13
+
14
+ ```typescript
15
+ @Cacheable({ key: 'user:#id', ttl: 3600 })
16
+ async findOne(id: number) {
17
+ // 首次调用会执行方法并缓存结果
18
+ // 后续调用直接返回缓存数据
19
+ }
20
+ ```
21
+
22
+ ### @CacheEvict - 清除缓存
23
+
24
+ ```typescript
25
+ @CacheEvict({ key: 'user:#id' })
26
+ async update(id: number, data: any) {
27
+ // 方法执行后会清除对应的缓存
28
+ }
29
+ ```
30
+
31
+ ### @CachePut - 更新缓存
32
+
33
+ ```typescript
34
+ @CachePut({ key: 'user:#id' })
35
+ async updateAndRefresh(id: number, data: any) {
36
+ // 方法执行后会用返回值更新缓存
37
+ }
38
+ ```
39
+
40
+ ## Key 表达式
41
+
42
+ - `#id` - 从 request.params 获取
43
+ - `#0`, `#1` - 位置参数
44
+ - `#paramName` - 参数名
45
+ - `#body.field` - 从 request.body 获取
46
+ - `user:*` - 通配符模式(需要 pattern: true)
47
+
48
+ ## 测试接口
49
+
50
+ 启动项目后,可以通过以下接口测试:
51
+
52
+ ```bash
53
+ # 查询用户(首次会查询数据库,后续返回缓存)
54
+ curl http://localhost:3000/examples/users/1
55
+
56
+ # 查询用户列表
57
+ curl http://localhost:3000/examples/users?page=1&pageSize=10
58
+
59
+ # 更新用户(会清除缓存)
60
+ curl -X PUT http://localhost:3000/examples/users/1 \
61
+ -H "Content-Type: application/json" \
62
+ -d '{"name":"New Name"}'
63
+
64
+ # 更新用户并刷新缓存
65
+ curl -X PUT http://localhost:3000/examples/users/1/refresh \
66
+ -H "Content-Type: application/json" \
67
+ -d '{"name":"New Name"}'
68
+
69
+ # 清除所有用户缓存
70
+ curl -X DELETE http://localhost:3000/examples/users/cache
71
+ ```
72
+
73
+ ## 最佳实践
74
+
75
+ 1. **合理设置 TTL**:根据数据更新频率设置过期时间
76
+ 2. **及时清除缓存**:更新/删除操作使用 @CacheEvict
77
+ 3. **避免缓存穿透**:对空结果也进行缓存
78
+ 4. **使用命名空间**:key 前缀区分不同业务
79
+
80
+ ## 更多信息
81
+
82
+ 查看官方文档:https://751848178.github.io/svton/packages/nestjs-cache
@@ -0,0 +1,42 @@
1
+ import { Controller, Get, Put, Delete, Param, Body, Query } from '@nestjs/common';
2
+ import { UserService, User } from './user.service';
3
+
4
+ @Controller('examples/users')
5
+ export class UserController {
6
+ constructor(private readonly userService: UserService) {}
7
+
8
+ @Get(':id')
9
+ async findOne(@Param('id') id: string): Promise<User> {
10
+ return this.userService.findOne(Number(id));
11
+ }
12
+
13
+ @Get()
14
+ async findAll(
15
+ @Query('page') page: string = '1',
16
+ @Query('pageSize') pageSize: string = '10',
17
+ ): Promise<User[]> {
18
+ return this.userService.findAll(Number(page), Number(pageSize));
19
+ }
20
+
21
+ @Put(':id')
22
+ async update(
23
+ @Param('id') id: string,
24
+ @Body() data: Partial<User>,
25
+ ): Promise<User> {
26
+ return this.userService.update(Number(id), data);
27
+ }
28
+
29
+ @Put(':id/refresh')
30
+ async updateAndRefresh(
31
+ @Param('id') id: string,
32
+ @Body() data: Partial<User>,
33
+ ): Promise<User> {
34
+ return this.userService.updateAndRefresh(Number(id), data);
35
+ }
36
+
37
+ @Delete('cache')
38
+ async clearCache(): Promise<{ message: string }> {
39
+ await this.userService.clearAllCache();
40
+ return { message: 'Cache cleared successfully' };
41
+ }
42
+ }
@@ -0,0 +1,78 @@
1
+ import { Injectable } from '@nestjs/common';
2
+ import { Cacheable, CacheEvict, CachePut } from '@svton/nestjs-cache';
3
+
4
+ export interface User {
5
+ id: number;
6
+ name: string;
7
+ email: string;
8
+ }
9
+
10
+ @Injectable()
11
+ export class UserService {
12
+ /**
13
+ * 缓存查询结果
14
+ * key: user:#id 会自动从参数中获取 id 值
15
+ * ttl: 缓存过期时间(秒)
16
+ */
17
+ @Cacheable({ key: 'user:#id', ttl: 3600 })
18
+ async findOne(id: number): Promise<User> {
19
+ console.log(`Fetching user ${id} from database...`);
20
+ // TODO: 实际项目中从数据库查询
21
+ return {
22
+ id,
23
+ name: `User ${id}`,
24
+ email: `user${id}@example.com`,
25
+ };
26
+ }
27
+
28
+ /**
29
+ * 更新数据时清除缓存
30
+ */
31
+ @CacheEvict({ key: 'user:#id' })
32
+ async update(id: number, data: Partial<User>): Promise<User> {
33
+ console.log(`Updating user ${id}...`);
34
+ // TODO: 实际项目中更新数据库
35
+ return {
36
+ id,
37
+ name: data.name || `User ${id}`,
38
+ email: data.email || `user${id}@example.com`,
39
+ };
40
+ }
41
+
42
+ /**
43
+ * 更新数据并刷新缓存
44
+ */
45
+ @CachePut({ key: 'user:#id' })
46
+ async updateAndRefresh(id: number, data: Partial<User>): Promise<User> {
47
+ console.log(`Updating and refreshing cache for user ${id}...`);
48
+ // TODO: 实际项目中更新数据库
49
+ return {
50
+ id,
51
+ name: data.name || `User ${id}`,
52
+ email: data.email || `user${id}@example.com`,
53
+ };
54
+ }
55
+
56
+ /**
57
+ * 批量清除缓存(使用通配符)
58
+ */
59
+ @CacheEvict({ key: 'user:*', pattern: true })
60
+ async clearAllCache(): Promise<void> {
61
+ console.log('Clearing all user cache...');
62
+ }
63
+
64
+ /**
65
+ * 列表查询缓存
66
+ * 支持多个参数
67
+ */
68
+ @Cacheable({ key: 'users:list:#page:#pageSize', ttl: 300 })
69
+ async findAll(page: number, pageSize: number): Promise<User[]> {
70
+ console.log(`Fetching users page ${page}...`);
71
+ // TODO: 实际项目中从数据库查询
72
+ return Array.from({ length: pageSize }, (_, i) => ({
73
+ id: (page - 1) * pageSize + i + 1,
74
+ name: `User ${(page - 1) * pageSize + i + 1}`,
75
+ email: `user${(page - 1) * pageSize + i + 1}@example.com`,
76
+ }));
77
+ }
78
+ }
@@ -0,0 +1,192 @@
1
+ # OAuth 登录示例
2
+
3
+ 本示例展示如何使用 `@svton/nestjs-oauth` 模块实现微信登录。
4
+
5
+ ## 文件说明
6
+
7
+ - `auth.service.ts` - 认证服务,处理 OAuth 流程
8
+ - `auth.controller.ts` - 认证控制器,提供登录接口
9
+
10
+ ## 支持的登录方式
11
+
12
+ ### 1. 微信开放平台(PC 扫码登录)
13
+
14
+ 适用于网站应用,用户扫码登录。
15
+
16
+ ### 2. 微信公众号(网页授权)
17
+
18
+ 适用于公众号内网页,静默授权或用户授权。
19
+
20
+ ### 3. 微信小程序
21
+
22
+ 适用于小程序,使用 wx.login() 获取 code。
23
+
24
+ ## 使用方式
25
+
26
+ ### PC 扫码登录
27
+
28
+ ```typescript
29
+ // 1. 获取授权 URL
30
+ const url = this.authService.getWechatOpenAuthUrl('/dashboard');
31
+
32
+ // 2. 重定向到微信授权页面
33
+ // 用户扫码授权后,微信会回调到 callback 接口
34
+
35
+ // 3. 处理回调
36
+ const userInfo = await this.authService.handleWechatOpenCallback(code);
37
+ ```
38
+
39
+ ### 公众号网页授权
40
+
41
+ ```typescript
42
+ // 1. 获取授权 URL
43
+ const url = this.authService.getWechatMpAuthUrl('/profile');
44
+
45
+ // 2. 重定向到微信授权页面
46
+ // 用户授权后,微信会回调到 callback 接口
47
+
48
+ // 3. 处理回调
49
+ const userInfo = await this.authService.handleWechatMpCallback(code);
50
+ ```
51
+
52
+ ### 小程序登录
53
+
54
+ ```typescript
55
+ // 小程序端
56
+ wx.login({
57
+ success: (res) => {
58
+ // 将 code 发送到后端
59
+ wx.request({
60
+ url: 'https://api.example.com/examples/auth/wechat/miniprogram/login',
61
+ method: 'POST',
62
+ data: { code: res.code },
63
+ });
64
+ },
65
+ });
66
+
67
+ // 后端处理
68
+ const result = await this.authService.miniprogramLogin(code);
69
+ ```
70
+
71
+ ### 小程序获取手机号
72
+
73
+ ```typescript
74
+ // 小程序端
75
+ <button open-type="getPhoneNumber" @getphonenumber="getPhoneNumber">
76
+ 获取手机号
77
+ </button>
78
+
79
+ // 获取到 code 后发送到后端
80
+ const result = await this.authService.getMiniprogramPhoneNumber(code);
81
+ ```
82
+
83
+ ## 测试接口
84
+
85
+ ### PC 扫码登录
86
+
87
+ ```bash
88
+ # 1. 访问登录页面(会重定向到微信)
89
+ curl http://localhost:3000/examples/auth/wechat/open/login
90
+
91
+ # 2. 扫码授权后,微信会回调到:
92
+ # http://localhost:3000/examples/auth/wechat/open/callback?code=xxx&state=/
93
+ ```
94
+
95
+ ### 小程序登录
96
+
97
+ ```bash
98
+ curl -X POST http://localhost:3000/examples/auth/wechat/miniprogram/login \
99
+ -H "Content-Type: application/json" \
100
+ -d '{"code":"081234567890abcdef"}'
101
+ ```
102
+
103
+ ### 小程序获取手机号
104
+
105
+ ```bash
106
+ curl -X POST http://localhost:3000/examples/auth/wechat/miniprogram/phone \
107
+ -H "Content-Type: application/json" \
108
+ -d '{"code":"081234567890abcdef"}'
109
+ ```
110
+
111
+ ## 环境变量配置
112
+
113
+ 在 `.env` 文件中配置:
114
+
115
+ ```env
116
+ # 微信开放平台
117
+ WECHAT_OPEN_APP_ID=wx1234567890abcdef
118
+ WECHAT_OPEN_APP_SECRET=1234567890abcdef1234567890abcdef
119
+ WECHAT_OPEN_CALLBACK_URL=https://yourdomain.com/examples/auth/wechat/open/callback
120
+
121
+ # 微信公众号
122
+ WECHAT_MP_APP_ID=wx1234567890abcdef
123
+ WECHAT_MP_APP_SECRET=1234567890abcdef1234567890abcdef
124
+
125
+ # 微信小程序
126
+ WECHAT_MINI_APP_ID=wx1234567890abcdef
127
+ WECHAT_MINI_APP_SECRET=1234567890abcdef1234567890abcdef
128
+ ```
129
+
130
+ ## 配置回调地址
131
+
132
+ 需要在微信后台配置回调地址:
133
+
134
+ ### 开放平台
135
+ 1. 登录微信开放平台
136
+ 2. 进入网站应用详情
137
+ 3. 配置授权回调域:`yourdomain.com`
138
+
139
+ ### 公众号
140
+ 1. 登录微信公众平台
141
+ 2. 设置与开发 -> 接口权限 -> 网页授权
142
+ 3. 配置授权回调域:`yourdomain.com`
143
+
144
+ ### 小程序
145
+ 1. 登录微信小程序后台
146
+ 2. 开发 -> 开发管理 -> 开发设置
147
+ 3. 配置服务器域名:`https://yourdomain.com`
148
+
149
+ ## 最佳实践
150
+
151
+ 1. **UnionID 机制**:使用 unionid 关联同一用户在不同应用的身份
152
+ 2. **Token 管理**:使用 JWT 生成 token,设置合理的过期时间
153
+ 3. **刷新机制**:实现 refresh_token 机制,避免频繁授权
154
+ 4. **错误处理**:妥善处理授权失败、token 过期等异常情况
155
+ 5. **安全性**:验证 state 参数,防止 CSRF 攻击
156
+
157
+ ## 常见场景
158
+
159
+ ### 网站登录
160
+
161
+ ```typescript
162
+ // 1. 用户点击"微信登录"
163
+ // 2. 重定向到微信授权页面
164
+ // 3. 用户扫码授权
165
+ // 4. 微信回调到后端
166
+ // 5. 后端获取用户信息,生成 token
167
+ // 6. 重定向到前端,携带 token
168
+ ```
169
+
170
+ ### 小程序登录
171
+
172
+ ```typescript
173
+ // 1. 小程序调用 wx.login() 获取 code
174
+ // 2. 将 code 发送到后端
175
+ // 3. 后端调用 code2Session 获取 openid
176
+ // 4. 查询或创建用户
177
+ // 5. 返回 token 给小程序
178
+ ```
179
+
180
+ ### 绑定手机号
181
+
182
+ ```typescript
183
+ // 1. 用户点击"获取手机号"按钮
184
+ // 2. 小程序获取到 code
185
+ // 3. 将 code 发送到后端
186
+ // 4. 后端调用接口获取手机号
187
+ // 5. 绑定手机号到用户账号
188
+ ```
189
+
190
+ ## 更多信息
191
+
192
+ 查看官方文档:https://751848178.github.io/svton/packages/nestjs-oauth
@@ -0,0 +1,99 @@
1
+ import { Controller, Get, Query, Redirect, Post, Body } from '@nestjs/common';
2
+ import { AuthService } from './auth.service';
3
+
4
+ @Controller('examples/auth')
5
+ export class AuthController {
6
+ constructor(private readonly authService: AuthService) {}
7
+
8
+ /**
9
+ * 微信开放平台登录 - 获取授权 URL
10
+ */
11
+ @Get('wechat/open/login')
12
+ @Redirect()
13
+ wechatOpenLogin(@Query('redirect') redirect?: string) {
14
+ const state = redirect || '/';
15
+ const url = this.authService.getWechatOpenAuthUrl(state);
16
+
17
+ return { url };
18
+ }
19
+
20
+ /**
21
+ * 微信开放平台登录 - 回调处理
22
+ */
23
+ @Get('wechat/open/callback')
24
+ async wechatOpenCallback(
25
+ @Query('code') code: string,
26
+ @Query('state') state: string,
27
+ ) {
28
+ const userInfo = await this.authService.handleWechatOpenCallback(code);
29
+
30
+ // TODO: 生成 JWT token
31
+ // const token = await this.jwtService.sign({ userId: user.id });
32
+
33
+ return {
34
+ message: 'Login successful',
35
+ userInfo,
36
+ redirectUrl: state,
37
+ // token,
38
+ };
39
+ }
40
+
41
+ /**
42
+ * 微信公众号登录 - 获取授权 URL
43
+ */
44
+ @Get('wechat/mp/login')
45
+ @Redirect()
46
+ wechatMpLogin(@Query('redirect') redirect?: string) {
47
+ const state = redirect || '/';
48
+ const url = this.authService.getWechatMpAuthUrl(state);
49
+
50
+ return { url };
51
+ }
52
+
53
+ /**
54
+ * 微信公众号登录 - 回调处理
55
+ */
56
+ @Get('wechat/mp/callback')
57
+ async wechatMpCallback(
58
+ @Query('code') code: string,
59
+ @Query('state') state: string,
60
+ ) {
61
+ const userInfo = await this.authService.handleWechatMpCallback(code);
62
+
63
+ return {
64
+ message: 'Login successful',
65
+ userInfo,
66
+ redirectUrl: state,
67
+ };
68
+ }
69
+
70
+ /**
71
+ * 小程序登录
72
+ */
73
+ @Post('wechat/miniprogram/login')
74
+ async miniprogramLogin(@Body() body: { code: string }) {
75
+ const result = await this.authService.miniprogramLogin(body.code);
76
+
77
+ // TODO: 生成 JWT token
78
+ // const token = await this.jwtService.sign({ userId: user.id });
79
+
80
+ return {
81
+ message: 'Login successful',
82
+ ...result,
83
+ // token,
84
+ };
85
+ }
86
+
87
+ /**
88
+ * 小程序获取手机号
89
+ */
90
+ @Post('wechat/miniprogram/phone')
91
+ async getMiniprogramPhone(@Body() body: { code: string }) {
92
+ const result = await this.authService.getMiniprogramPhoneNumber(body.code);
93
+
94
+ return {
95
+ message: 'Phone number retrieved successfully',
96
+ ...result,
97
+ };
98
+ }
99
+ }
@@ -0,0 +1,97 @@
1
+ import { Injectable } from '@nestjs/common';
2
+ import { OAuthService } from '@svton/nestjs-oauth';
3
+
4
+ @Injectable()
5
+ export class AuthService {
6
+ constructor(private readonly oauthService: OAuthService) {}
7
+
8
+ /**
9
+ * 获取微信开放平台授权 URL(PC 扫码登录)
10
+ */
11
+ getWechatOpenAuthUrl(state: string): string {
12
+ return this.oauthService.wechat.getAuthorizationUrl('open', state);
13
+ }
14
+
15
+ /**
16
+ * 获取微信公众号授权 URL(网页授权)
17
+ */
18
+ getWechatMpAuthUrl(state: string): string {
19
+ return this.oauthService.wechat.getAuthorizationUrl('mp', state);
20
+ }
21
+
22
+ /**
23
+ * 处理微信开放平台回调
24
+ */
25
+ async handleWechatOpenCallback(code: string) {
26
+ // 获取 access_token
27
+ const tokenResult = await this.oauthService.wechat.getAccessToken('open', code);
28
+
29
+ // 获取用户信息
30
+ const userInfo = await this.oauthService.wechat.getUserInfo(
31
+ 'open',
32
+ tokenResult.access_token,
33
+ tokenResult.openid,
34
+ );
35
+
36
+ // TODO: 根据 openid 查询或创建用户
37
+ // const user = await this.userService.findOrCreateByWechatOpenId(userInfo.unionid);
38
+
39
+ return {
40
+ openid: userInfo.openid,
41
+ unionid: userInfo.unionid,
42
+ nickname: userInfo.nickname,
43
+ avatar: userInfo.headimgurl,
44
+ };
45
+ }
46
+
47
+ /**
48
+ * 处理微信公众号回调
49
+ */
50
+ async handleWechatMpCallback(code: string) {
51
+ const tokenResult = await this.oauthService.wechat.getAccessToken('mp', code);
52
+
53
+ const userInfo = await this.oauthService.wechat.getUserInfo(
54
+ 'mp',
55
+ tokenResult.access_token,
56
+ tokenResult.openid,
57
+ );
58
+
59
+ // TODO: 根据 openid 查询或创建用户
60
+ // const user = await this.userService.findOrCreateByWechatMpOpenId(userInfo.openid);
61
+
62
+ return {
63
+ openid: userInfo.openid,
64
+ nickname: userInfo.nickname,
65
+ avatar: userInfo.headimgurl,
66
+ };
67
+ }
68
+
69
+ /**
70
+ * 小程序登录
71
+ */
72
+ async miniprogramLogin(code: string) {
73
+ const result = await this.oauthService.wechat.code2Session(code);
74
+
75
+ // TODO: 根据 openid 查询或创建用户
76
+ // const user = await this.userService.findOrCreateByWechatMiniOpenId(result.openid);
77
+
78
+ return {
79
+ openid: result.openid,
80
+ sessionKey: result.session_key,
81
+ unionid: result.unionid,
82
+ };
83
+ }
84
+
85
+ /**
86
+ * 小程序获取手机号
87
+ */
88
+ async getMiniprogramPhoneNumber(code: string) {
89
+ const result = await this.oauthService.wechat.getPhoneNumber(code);
90
+
91
+ return {
92
+ phoneNumber: result.phone_info.phoneNumber,
93
+ purePhoneNumber: result.phone_info.purePhoneNumber,
94
+ countryCode: result.phone_info.countryCode,
95
+ };
96
+ }
97
+ }