Skip to content

Commit

Permalink
feat: 登陆接口
Browse files Browse the repository at this point in the history
  • Loading branch information
LiuSongJiang committed Apr 8, 2019
1 parent 59d4ba8 commit a9836ad
Show file tree
Hide file tree
Showing 16 changed files with 246 additions and 48 deletions.
2 changes: 1 addition & 1 deletion config.dev.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"wxSecret": "a413d6545e63a35fc2a0336d66534b89"
},
"server": {
"port": 5000,
"port": 3000,
"host": "localhost",
"jwtSecret": "random-secret-password",
"jwtExpiration": "1h",
Expand Down
11 changes: 2 additions & 9 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
"license": "ISC",
"dependencies": {
"axios": "^0.18.0",
"boom": "^7.3.0",
"hapi": "^18.0.0",
"hapi-auth-jwt2": "^8.3.0",
"hapi-pagination": "^2.0.0",
Expand Down
Empty file removed src/api/hapi/controller.ts
Empty file.
19 changes: 0 additions & 19 deletions src/api/hapi/routes.ts

This file was deleted.

Empty file removed src/api/hapi/validator.ts
Empty file.
35 changes: 18 additions & 17 deletions src/api/index.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,26 @@
import * as requireAll from 'require-all';
import * as requireAll from "require-all";
import * as Hapi from "hapi";

// 自动引入routes.js
const allApi: object = requireAll({
dirname: __dirname,
filter: /routes.js$/,
})
filter: /routes.js$/
});

const routes = [];
export default (server: Hapi.Server): Hapi.Server => {
const routes = [];

function reduceRoute(routeNote: object) {
Object.values(routeNote).forEach((route = {}) => {
const module = route['routes.js'] || [];
if (module) {
const [moduleDefault] = module.default
routes.push(moduleDefault);
}
});
}
function reduceRoute(routeNote: object) {
Object.values(routeNote).forEach((route = {}) => {
const module = route["routes.js"];
if (module) {
const [moduleDefault] = module.default(server);
routes.push(moduleDefault);
}
});
}

reduceRoute(allApi)


export default routes;
reduceRoute(allApi);

return server.route(routes);
};
79 changes: 79 additions & 0 deletions src/api/wxLogin/controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import * as Hapi from "hapi";
import axios from "axios";
import * as JWT from "jsonwebtoken";
import { IMixinsServerWechat } from "../../interfaces/config";
import { IWxLoginRquest, IWxLoginParams } from "./interfaces";
import decryptData from "../../utils/decrypted-data";
import Users, { IUsers } from "../../db/models/users.model";

export default class WxLoginController {
private config: IMixinsServerWechat;
private model = Users;
private wxSessionUrl = "https://api.weixin.qq.com/sns/jscode2session";
private grant_type = "authorization_code";

constructor(conifg: IMixinsServerWechat) {
this.config = conifg;
}

public async wxLogin(request: IWxLoginRquest, h: Hapi.ResponseToolkit) {
const appid = this.config.wxAppid; // 小程序 appid
const secret = this.config.wxSecret; // 小程序 appsecret

const { code, encryptedData, iv } = request.payload;

const { openid, sessionKey } = await this.getOpid({
appid,
secret,
js_code: code,
grant_type: this.grant_type
});

const user = await this.model.findOrCreate({
where: { open_id: openid }
});

const userInfo = decryptData(encryptedData, iv, sessionKey, appid);

await this.updateUser(
{
nick_name: userInfo.nickName,
gender: userInfo.gender,
avatar_url: userInfo.avatarUrl,
open_id: openid,
session_key: sessionKey
},
openid
);

const token = { token: this.generateToken(user[0].id) };

return token;
}

public async updateUser(updateData: IUsers, openid: string) {
await this.model.update(updateData, {
where: { open_id: openid }
});
}

private generateToken(userId: number) {
const payload = {
id: userId
};
const jwtSecret = this.config.jwtSecret;
const jwtExpiration = this.config.jwtExpiration;
return JWT.sign(payload, jwtSecret, { expiresIn: jwtExpiration });
}

// 通过微信服务器获取opid
private async getOpid(params: IWxLoginParams) {
const response = await axios({
url: this.wxSessionUrl,
method: "GET",
params
});
const { openid, session_key: sessionKey } = response.data;
return { openid, sessionKey };
}
}
15 changes: 15 additions & 0 deletions src/api/wxLogin/interfaces.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
export interface IWxLoginRquest {
payload: {
code: string;
encryptedData: string;
iv: string;
};
}

export interface IWxLoginParams {
appid: string;
secret: string;
js_code: string;
grant_type: string;
}

33 changes: 33 additions & 0 deletions src/api/wxLogin/routes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import * as Hapi from "hapi";
import { getWeChatConfigs, getServerConfigs } from "../../configurations";
import {
IMixinsServerWechat,
IWeChatConifg,
IServerConfigurations
} from "../../interfaces/config";
import WxLoginController from "./controller";
import { wxLogin } from "./validator";

export default (server: Hapi.Server) => {
const weChatConfig: IWeChatConifg = getWeChatConfigs();
const serverConfig: IServerConfigurations = getServerConfigs();
const configs: IMixinsServerWechat = { ...weChatConfig, ...serverConfig };
const loginController = new WxLoginController(configs);
server.bind(loginController);

return [
{
method: "POST",
path: "/wxLogin",
handler: loginController.wxLogin,
config: {
auth: false, // 不需要用户验证
tags: ["api", "GROUP_NAME"],
description: "创建订单",
validate: {
payload: wxLogin
}
}
}
];
};
15 changes: 15 additions & 0 deletions src/api/wxLogin/validator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import * as Joi from "joi";

export const wxLogin = Joi.object().keys({
code: Joi.string()
.required()
.description("微信用户登录的临时code"),

encryptedData: Joi.string()
.required()
.description("微信用户信息encryptedData"),

iv: Joi.string()
.required()
.description("微信用户信息iv")
});
9 changes: 9 additions & 0 deletions src/db/models/users.model.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
import { Table, Column, Model } from "sequelize-typescript";

export interface IUsers {
id?: number;
nick_name: string;
avatar_url: string;
gender: number;
open_id: string;
session_key: string;
}

@Table({
tableName: "users",
timestamps: false
Expand Down
4 changes: 4 additions & 0 deletions src/interfaces/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ export interface IServerConfigurations {
routePrefix: string;
}

export interface IMixinsServerWechat
extends IWeChatConifg,
IServerConfigurations {}

export interface IDataConfiguration {
url: string;
}
Expand Down
4 changes: 2 additions & 2 deletions src/server.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import * as Hapi from "hapi";
import { IServerConfigurations } from "./interfaces/config";
import apis from "./api";
import registerRoute from "./api";
import registerModels from "./db/models";

export async function init(
Expand Down Expand Up @@ -43,7 +43,7 @@ export async function init(
// 注册model
registerModels();
// 注册路由
server.route(apis);
registerRoute(server);

return server;
}
52 changes: 52 additions & 0 deletions src/utils/decrypted-data.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// 封装的 decryptData,用于解码小程序的 encryptData
import * as crypto from "crypto";

export interface IDecodeUserData {
avatarUrl: string;
gender: number;
nickName: string;
watermark: {
appid: string;
};
}

function decryptData(
encryptedData: string,
iv: string,
sessionKey: string,
appid: string
) {
// base64 decode
const encryptedDataNew = Buffer.from(encryptedData, "base64");
const sessionKeyNew = Buffer.from(sessionKey, "base64");
const ivNew = Buffer.from(iv, "base64");

let decoded = "";
let decodeData: IDecodeUserData;
try {
// 解密,使用的算法是 aes-128-cbc
const decipher = crypto.createDecipheriv(
"aes-128-cbc",
sessionKeyNew,
ivNew
);
// 设置自动 padding 为 true,删除填充补位
decipher.setAutoPadding(true);
decoded = decipher.update(encryptedDataNew, undefined, "utf8");
decoded += decipher.final("utf8");
decodeData = JSON.parse(decoded);
// decoded 是解密后的用户信息
} catch (err) {
throw new Error("Illegal Buffer" + err);
}

// 解密后的用户数据中会有一个 watermark 属性,这个属性中包含这个小程序的 appid 和时间戳,下面是校验 appid
if (decodeData.watermark.appid !== appid) {
throw new Error("Illegal Buffer");
}

// 返回解密后的用户数据
return decodeData;
}

export default decryptData;
15 changes: 15 additions & 0 deletions src/utils/router-helper.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import * as Joi from 'joi';

export const paginationDefine = {
limit: Joi.number().integer().min(1).default(10)
.description('每页的条目数'),
page: Joi.number().integer().min(1).default(1)
.description('页码数'),
pagination: Joi.boolean().description('是否开启分页,默认为true'),
}

export const jwtHeaderDefine = {
headers: Joi.object({
authorization: Joi.string().required(),
}).unknown(),
}

0 comments on commit a9836ad

Please sign in to comment.