From 4aed6f02b1bef1e8808876b9828f73a89353e698 Mon Sep 17 00:00:00 2001 From: cool Date: Wed, 27 Mar 2024 13:37:23 +0800 Subject: [PATCH] =?UTF-8?q?=E5=BE=AE=E4=BF=A1=E7=9B=B8=E5=85=B3=E6=8F=92?= =?UTF-8?q?=E4=BB=B6=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/README.md | 3 - src/modules/user/config.ts | 13 -- src/modules/user/controller/admin/address.ts | 2 + src/modules/user/controller/app/address.ts | 39 +++++ src/modules/user/controller/app/comm.ts | 9 +- src/modules/user/controller/app/info.ts | 19 +++ src/modules/user/controller/app/login.ts | 6 + src/modules/user/service/address.ts | 63 ++++++++ src/modules/user/service/info.ts | 40 ++++- src/modules/user/service/login.ts | 33 +++- src/modules/user/service/wx.ts | 158 +++++++++++++------ 11 files changed, 308 insertions(+), 77 deletions(-) delete mode 100644 packages/README.md create mode 100644 src/modules/user/controller/app/address.ts create mode 100644 src/modules/user/service/address.ts diff --git a/packages/README.md b/packages/README.md deleted file mode 100644 index c795a47..0000000 --- a/packages/README.md +++ /dev/null @@ -1,3 +0,0 @@ -核心包单独项目 - -请转到:https://github.com/cool-team-official/cool-admin-midway-packages 查看 \ No newline at end of file diff --git a/src/modules/user/config.ts b/src/modules/user/config.ts index 6e0946b..f0e14f3 100644 --- a/src/modules/user/config.ts +++ b/src/modules/user/config.ts @@ -21,19 +21,6 @@ export default () => { // 验证码有效期,单位秒 timeout: 60 * 3, }, - // 微信配置 - wx: { - // 小程序 - mini: { - appid: '', - secret: '', - }, - // 公众号 - mp: { - appid: '', - secret: '', - }, - }, // jwt jwt: { // token 过期时间,单位秒 diff --git a/src/modules/user/controller/admin/address.ts b/src/modules/user/controller/admin/address.ts index 0a49842..fa18ade 100644 --- a/src/modules/user/controller/admin/address.ts +++ b/src/modules/user/controller/admin/address.ts @@ -1,5 +1,6 @@ import { CoolController, BaseController } from '@cool-midway/core'; import { UserAddressEntity } from '../../entity/address'; +import { UserAddressService } from '../../service/address'; /** * 用户-地址 @@ -7,5 +8,6 @@ import { UserAddressEntity } from '../../entity/address'; @CoolController({ api: ['add', 'delete', 'update', 'info', 'list', 'page'], entity: UserAddressEntity, + service: UserAddressService, }) export class AdminUserAddressesController extends BaseController {} diff --git a/src/modules/user/controller/app/address.ts b/src/modules/user/controller/app/address.ts new file mode 100644 index 0000000..a10a7fc --- /dev/null +++ b/src/modules/user/controller/app/address.ts @@ -0,0 +1,39 @@ +import { Get, Inject, Provide } from '@midwayjs/decorator'; +import { CoolController, BaseController } from '@cool-midway/core'; +import { UserAddressEntity } from '../../entity/address'; +import { UserAddressService } from '../../service/address'; + +/** + * 地址 + */ +@Provide() +@CoolController({ + api: ['add', 'delete', 'update', 'info', 'list', 'page'], + entity: UserAddressEntity, + service: UserAddressService, + insertParam: ctx => { + return { + userId: ctx.user.id, + }; + }, + pageQueryOp: { + where: async ctx => { + return [['userId =:userId', { userId: ctx.user.id }]]; + }, + addOrderBy: { + isDefault: 'DESC', + }, + }, +}) +export class AppUserAddressController extends BaseController { + @Inject() + userAddressService: UserAddressService; + + @Inject() + ctx; + + @Get('/default', { summary: '默认地址' }) + async default() { + return this.ok(await this.userAddressService.default(this.ctx.user.id)); + } +} diff --git a/src/modules/user/controller/app/comm.ts b/src/modules/user/controller/app/comm.ts index 0de190f..be06d6c 100644 --- a/src/modules/user/controller/app/comm.ts +++ b/src/modules/user/controller/app/comm.ts @@ -5,7 +5,7 @@ import { TagTypes, CoolTag, } from '@cool-midway/core'; -import { Get, Inject, Query } from '@midwayjs/core'; +import { Body, Inject, Post } from '@midwayjs/core'; import { UserWxService } from '../../service/wx'; /** @@ -18,9 +18,8 @@ export class UserCommController extends BaseController { userWxService: UserWxService; @CoolTag(TagTypes.IGNORE_TOKEN) - @Get('/wxMpConfig', { summary: '获取微信公众号配置' }) - public async getWxMpConfig(@Query() url: string) { - const a = await this.userWxService.getWxMpConfig(url); - return this.ok(a); + @Post('/wxMpConfig', { summary: '获取微信公众号配置' }) + public async getWxMpConfig(@Body('url') url: string) { + return this.ok(await this.userWxService.getWxMpConfig(url)); } } diff --git a/src/modules/user/controller/app/info.ts b/src/modules/user/controller/app/info.ts index f18f692..b1a94ab 100644 --- a/src/modules/user/controller/app/info.ts +++ b/src/modules/user/controller/app/info.ts @@ -43,4 +43,23 @@ export class AppUserInfoController extends BaseController { await this.userInfoService.logoff(this.ctx.user.id); return this.ok(); } + + @Post('/bindPhone', { summary: '绑定手机号' }) + async bindPhone(@Body('phone') phone: string, @Body('code') code: string) { + await this.userInfoService.bindPhone(this.ctx.user.id, phone, code); + return this.ok(); + } + + @Post('/miniPhone', { summary: '绑定小程序手机号' }) + async miniPhone(@Body() body) { + const { code, encryptedData, iv } = body; + return this.ok( + await this.userInfoService.miniPhone( + this.ctx.user.id, + code, + encryptedData, + iv + ) + ); + } } diff --git a/src/modules/user/controller/app/login.ts b/src/modules/user/controller/app/login.ts index 1a7abad..efa1e4b 100644 --- a/src/modules/user/controller/app/login.ts +++ b/src/modules/user/controller/app/login.ts @@ -34,6 +34,12 @@ export class AppUserLoginController extends BaseController { return this.ok(await this.userLoginService.mp(code)); } + @CoolTag(TagTypes.IGNORE_TOKEN) + @Post('/wxApp', { summary: '微信APP授权登录' }) + async app(@Body('code') code: string) { + return this.ok(await this.userLoginService.wxApp(code)); + } + @CoolTag(TagTypes.IGNORE_TOKEN) @Post('/phone', { summary: '手机号登录' }) async phone(@Body('phone') phone: string, @Body('smsCode') smsCode: string) { diff --git a/src/modules/user/service/address.ts b/src/modules/user/service/address.ts new file mode 100644 index 0000000..6a3a3a7 --- /dev/null +++ b/src/modules/user/service/address.ts @@ -0,0 +1,63 @@ +import { Init, Inject, Provide } from '@midwayjs/decorator'; +import { BaseService } from '@cool-midway/core'; +import { Equal, Repository } from 'typeorm'; +import { UserAddressEntity } from '../entity/address'; +import { InjectEntityModel } from '@midwayjs/typeorm'; + +/** + * 地址 + */ +@Provide() +export class UserAddressService extends BaseService { + @InjectEntityModel(UserAddressEntity) + userAddressEntity: Repository; + + @Inject() + ctx; + + @Init() + async init() { + await super.init(); + this.setEntity(this.userAddressEntity); + } + + /** + * 列表信息 + */ + async list() { + return this.userAddressEntity + .createQueryBuilder() + .where('userId = :userId ', { userId: this.ctx.user.id }) + .addOrderBy('isDefault', 'DESC') + .getMany(); + } + + /** + * 修改之后 + * @param data + * @param type + */ + async modifyAfter(data: any, type: 'add' | 'update' | 'delete') { + if (type == 'add' || type == 'update') { + if (data.isDefault) { + await this.userAddressEntity + .createQueryBuilder() + .update() + .set({ isDefault: false }) + .where('userId = :userId ', { userId: this.ctx.user.id }) + .andWhere('id != :id', { id: data.id }) + .execute(); + } + } + } + + /** + * 默认地址 + */ + async default(userId) { + return await this.userAddressEntity.findOneBy({ + userId: Equal(userId), + isDefault: true, + }); + } +} diff --git a/src/modules/user/service/info.ts b/src/modules/user/service/info.ts index a6c8b2a..6a41e1f 100644 --- a/src/modules/user/service/info.ts +++ b/src/modules/user/service/info.ts @@ -1,12 +1,13 @@ import { Inject, Provide } from '@midwayjs/decorator'; import { BaseService, CoolCommException } from '@cool-midway/core'; import { InjectEntityModel } from '@midwayjs/typeorm'; -import { Repository } from 'typeorm'; +import { Equal, Repository } from 'typeorm'; import { UserInfoEntity } from '../entity/info'; import { v1 as uuid } from 'uuid'; import { UserSmsService } from './sms'; import * as md5 from 'md5'; import { PluginService } from '../../plugin/service/info'; +import { UserWxService } from './wx'; /** * 用户信息 @@ -22,13 +23,29 @@ export class UserInfoService extends BaseService { @Inject() userSmsService: UserSmsService; + @Inject() + userWxService: UserWxService; + + /** + * 绑定小程序手机号 + * @param userId + * @param code + * @param encryptedData + * @param iv + */ + async miniPhone(userId: number, code: any, encryptedData: any, iv: any) { + const phone = await this.userWxService.miniPhone(code, encryptedData, iv); + await this.userInfoEntity.update({ id: Equal(userId) }, { phone }); + return phone; + } + /** * 获取用户信息 * @param id * @returns */ async person(id) { - return await this.userInfoEntity.findOneBy({ id }); + return await this.userInfoEntity.findOneBy({ id: Equal(id) }); } /** @@ -55,8 +72,9 @@ export class UserInfoService extends BaseService { * @returns */ async updatePerson(id, param) { + const info = await this.person(id); + if (!info) throw new CoolCommException('用户不存在'); try { - const info = await this.person(id); // 修改了头像要重新处理 if (param.avatarUrl && info.avatarUrl != param.avatarUrl) { const file = await this.pluginService.getInstance('upload'); @@ -65,6 +83,8 @@ export class UserInfoService extends BaseService { uuid() + '.png' ); } + } catch (err) {} + try { return await this.userInfoEntity.update({ id }, param); } catch (err) { throw new CoolCommException('更新失败,参数错误或者手机号已存在'); @@ -85,4 +105,18 @@ export class UserInfoService extends BaseService { } await this.userInfoEntity.update(user.id, { password: md5(password) }); } + + /** + * 绑定手机号 + * @param userId + * @param phone + * @param code + */ + async bindPhone(userId, phone, code) { + const check = await this.userSmsService.checkCode(phone, code); + if (!check) { + throw new CoolCommException('验证码错误'); + } + await this.userInfoEntity.update({ id: userId }, { phone }); + } } diff --git a/src/modules/user/service/login.ts b/src/modules/user/service/login.ts index 5570347..5170118 100644 --- a/src/modules/user/service/login.ts +++ b/src/modules/user/service/login.ts @@ -1,7 +1,7 @@ import { Config, Inject, Provide } from '@midwayjs/decorator'; import { BaseService, CoolCommException } from '@cool-midway/core'; import { InjectEntityModel } from '@midwayjs/typeorm'; -import { Repository } from 'typeorm'; +import { Equal, Repository } from 'typeorm'; import { UserInfoEntity } from '../entity/info'; import { UserWxService } from './wx'; import * as jwt from 'jsonwebtoken'; @@ -62,7 +62,9 @@ export class UserLoginService extends BaseService { // 1、检查短信验证码 2、登录 const check = await this.userSmsService.checkCode(phone, smsCode); if (check) { - let user: any = await this.userInfoEntity.findOneBy({ phone }); + let user: any = await this.userInfoEntity.findOneBy({ + phone: Equal(phone), + }); if (!user) { user = { phone, @@ -105,6 +107,33 @@ export class UserLoginService extends BaseService { } } + /** + * 微信APP授权登录 + * @param code + */ + async wxApp(code: string) { + let wxUserInfo = await this.userWxService.appUserInfo(code); + if (wxUserInfo) { + delete wxUserInfo.privilege; + wxUserInfo = await this.saveWxInfo( + { + openid: wxUserInfo.openid, + unionid: wxUserInfo.unionid, + avatarUrl: wxUserInfo.headimgurl, + nickName: wxUserInfo.nickname, + gender: wxUserInfo.sex, + city: wxUserInfo.city, + province: wxUserInfo.province, + country: wxUserInfo.country, + }, + 1 + ); + return this.wxLoginToken(wxUserInfo); + } else { + throw new Error('微信登录失败'); + } + } + /** * 保存微信信息 * @param wxUserInfo diff --git a/src/modules/user/service/wx.ts b/src/modules/user/service/wx.ts index 416484f..528eabf 100644 --- a/src/modules/user/service/wx.ts +++ b/src/modules/user/service/wx.ts @@ -1,9 +1,14 @@ -import { Config, Provide } from '@midwayjs/decorator'; +import { Config, Init, Inject, Provide } from '@midwayjs/decorator'; import { BaseService, CoolCache, CoolCommException } from '@cool-midway/core'; import axios from 'axios'; import * as crypto from 'crypto'; import { v1 as uuid } from 'uuid'; import * as moment from 'moment'; +import { InjectEntityModel } from '@midwayjs/typeorm'; +import { Equal, Repository } from 'typeorm'; +import { UserInfoEntity } from '../entity/info'; +import { UserWxEntity } from '../entity/wx'; +import { PluginService } from '../../plugin/service/info'; /** * 微信 @@ -13,6 +18,66 @@ export class UserWxService extends BaseService { @Config('module.user') config; + @InjectEntityModel(UserInfoEntity) + userInfoEntity: Repository; + + @InjectEntityModel(UserWxEntity) + userWxEntity: Repository; + + @Inject() + pluginService: PluginService; + + /** + * 获得小程序实例 + * @returns + */ + async getMiniApp() { + const wxPlugin = await this.pluginService.getInstance('wx'); + return wxPlugin.MiniApp(); + } + + /** + * 获得公众号实例 + * @returns + */ + async getOfficialAccount() { + const wxPlugin = await this.pluginService.getInstance('wx'); + return wxPlugin.OfficialAccount(); + } + + /** + * 获得App实例 + * @returns + */ + async getOpenPlatform() { + const wxPlugin = await this.pluginService.getInstance('wx'); + return wxPlugin.OpenPlatform(); + } + + /** + * 获得用户的openId + * @param userId + * @param type 0-小程序 1-公众号 2-App + */ + async getOpenid(userId: number, type = 0) { + const user = await this.userInfoEntity.findOneBy({ + id: Equal(userId), + status: 1, + }); + if (!user) { + throw new CoolCommException('用户不存在或已被禁用'); + } + const wx = await this.userWxEntity + .createQueryBuilder('a') + .where('a.type = :type', { type }) + .andWhere('(a.unionid = :unionid or a.openid =:openid )', { + unionid: user.unionid, + openid: user.unionid, + }) + .getOne(); + return wx ? wx.openid : null; + } + /** * 获得微信配置 * @param appId @@ -30,7 +95,9 @@ export class UserWxService extends BaseService { }, } ); - const { appid } = this.config.wx.mp; + + const account = (await this.getOfficialAccount()).getAccount(); + const appid = account.getAppId(); // 返回结果集 const result = { timestamp: parseInt(moment().valueOf() / 1000 + ''), @@ -57,7 +124,16 @@ export class UserWxService extends BaseService { * @param code */ async mpUserInfo(code) { - const token = await this.openOrMpToken(code, this.config.wx.mp); + const token = await this.openOrMpToken(code, 'mp'); + return await this.openOrMpUserInfo(token); + } + + /** + * 获得app用户信息 + * @param code + */ + async appUserInfo(code) { + const token = await this.openOrMpToken(code, 'open'); return await this.openOrMpUserInfo(token); } @@ -66,18 +142,14 @@ export class UserWxService extends BaseService { * @param appid * @param secret */ - @CoolCache(3600) public async getWxToken(type = 'mp') { - //@ts-ignore - const conf = this.config.wx[type]; - const result = await axios.get('https://api.weixin.qq.com/cgi-bin/token', { - params: { - grant_type: 'client_credential', - appid: conf.appid, - secret: conf.secret, - }, - }); - return result.data; + let app; + if (type == 'mp') { + app = await this.getOfficialAccount(); + } else { + app = await this.getOpenPlatform(); + } + return await app.getAccessToken().getToken(); } /** @@ -101,15 +173,19 @@ export class UserWxService extends BaseService { /** * 获得token嗯 * @param code - * @param conf + * @param type */ - async openOrMpToken(code, conf) { + async openOrMpToken(code, type = 'mp') { + const account = + type == 'mp' + ? (await this.getOfficialAccount()).getAccount() + : (await this.getMiniApp()).getAccount(); const result = await axios.get( 'https://api.weixin.qq.com/sns/oauth2/access_token', { params: { - appid: conf.appid, - secret: conf.secret, + appid: account.getAppId(), + secret: account.getSecret(), code, grant_type: 'authorization_code', }, @@ -124,20 +200,10 @@ export class UserWxService extends BaseService { * @param conf 配置 */ async miniSession(code) { - const { appid, secret } = this.config.wx.mini; - const result = await axios.get( - 'https://api.weixin.qq.com/sns/jscode2session', - { - params: { - appid, - secret, - js_code: code, - grant_type: 'authorization_code', - }, - } - ); - - return result.data; + const app = await this.getMiniApp(); + const utils = app.getUtils(); + const result = await utils.codeToSession(code); + return result; } /** @@ -178,7 +244,12 @@ export class UserWxService extends BaseService { if (session.errcode) { throw new CoolCommException('获取手机号失败,请刷新重试'); } - return await this.miniDecryptData(encryptedData, iv, session.session_key); + const result = await this.miniDecryptData( + encryptedData, + iv, + session.session_key + ); + return result.phoneNumber; } /** @@ -188,23 +259,8 @@ export class UserWxService extends BaseService { * @param sessionKey */ async miniDecryptData(encryptedData, iv, sessionKey) { - sessionKey = Buffer.from(sessionKey, 'base64'); - encryptedData = Buffer.from(encryptedData, 'base64'); - iv = Buffer.from(iv, 'base64'); - try { - // 解密 - const decipher = crypto.createDecipheriv('aes-128-cbc', sessionKey, iv); - // 设置自动 padding 为 true,删除填充补位 - decipher.setAutoPadding(true); - // @ts-ignore - let decoded = decipher.update(encryptedData, 'binary', 'utf8'); - // @ts-ignore - decoded += decipher.final('utf8'); - // @ts-ignore - decoded = JSON.parse(decoded); - return decoded; - } catch (err) { - throw new CoolCommException('获得信息失败'); - } + const app = await this.getMiniApp(); + const utils = app.getUtils(); + return await utils.decryptSession(sessionKey, iv, encryptedData); } }