微信相关插件化

This commit is contained in:
cool 2024-03-27 13:37:23 +08:00
parent aab2c47898
commit 4aed6f02b1
11 changed files with 308 additions and 77 deletions

View File

@ -1,3 +0,0 @@
核心包单独项目
请转到https://github.com/cool-team-official/cool-admin-midway-packages 查看

View File

@ -21,19 +21,6 @@ export default () => {
// 验证码有效期,单位秒
timeout: 60 * 3,
},
// 微信配置
wx: {
// 小程序
mini: {
appid: '',
secret: '',
},
// 公众号
mp: {
appid: '',
secret: '',
},
},
// jwt
jwt: {
// token 过期时间,单位秒

View File

@ -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 {}

View File

@ -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));
}
}

View File

@ -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));
}
}

View File

@ -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
)
);
}
}

View File

@ -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) {

View File

@ -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<UserAddressEntity>;
@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,
});
}
}

View File

@ -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 });
}
}

View File

@ -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

View File

@ -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<UserInfoEntity>;
@InjectEntityModel(UserWxEntity)
userWxEntity: Repository<UserWxEntity>;
@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);
}
}