diff --git a/initProject.ps1 b/initProject.ps1 new file mode 100644 index 0000000..e2bf85c --- /dev/null +++ b/initProject.ps1 @@ -0,0 +1,2 @@ +nvm +yarn \ No newline at end of file diff --git a/package.json b/package.json index 384f706..2fbd970 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "koa2-template", - "version": "1.0.0", + "version": "1.0.1", "description": "init a project with me", "main": "index.js", "scripts": { diff --git a/public/js/.example.js b/public/js/.example.js new file mode 100644 index 0000000..e69de29 diff --git a/scripts/clean.ps1 b/scripts/clean.ps1 new file mode 100644 index 0000000..f5074be --- /dev/null +++ b/scripts/clean.ps1 @@ -0,0 +1 @@ +# 删除项目中的 占位/示例 文件(.example.*),不影响项目内容 diff --git a/src/controller/user.js b/src/controller/user.js new file mode 100644 index 0000000..dda5092 --- /dev/null +++ b/src/controller/user.js @@ -0,0 +1,12 @@ +const { d } = require("../models/user"); + +class UserController { + async register(ctx, next) { + ctx.json({ code: 0, message: "success" }); + } + + async getUserList(ctx, next) { + ctx.json({ code: 0, message: "success" }); + } +} +module.exports = new UserController(); diff --git a/src/data/upload/.example b/src/data/upload/.example new file mode 100644 index 0000000..e69de29 diff --git a/src/db/mariadb.js b/src/db/mariadb.js new file mode 100644 index 0000000..1b5cf40 --- /dev/null +++ b/src/db/mariadb.js @@ -0,0 +1,27 @@ +// connect to third-service of database or other. +const { Sequelize } = require("sequelize"); + +const { + MARIA_DIALECT, + MARIA_HOST, + MARIA_PORT, + MARIA_DBNAME, + MARIA_USRNAME, + MARIA_PASSWD, +} = process.env; + +const seq = new Sequelize(MARIA_DBNAME, MARIA_USRNAME, MARIA_PASSWD, { + host: MARIA_HOST, + port: MARIA_PORT, + dialect: 'mariadb', +}); + +seq.authenticate() + .then(() => { + console.log('数据库连接成功') + }) + .catch(err => { + console.log('数据库连接失败', err) + }) + +module.exports = seq; diff --git a/src/db/redis.js b/src/db/redis.js new file mode 100644 index 0000000..c81cbdc --- /dev/null +++ b/src/db/redis.js @@ -0,0 +1,49 @@ +const redis = require("redis"); +// require("../config/env.config"); + +const { REDIS_HOST, REDIS_PORT, REDIS_DBNAME, REDIS_USER, REDIS_PASSWD } = + process.env; // REDIS_USER 和 REDIS_PASSWD,需要服务器配置ACL用户名密码 +const URL = `redis://${REDIS_USER}:${REDIS_PASSWD}@${REDIS_HOST}:${REDIS_PORT}/${REDIS_DBNAME}`; + +// https://github.com/redis/node-redis/tree/master/docs +// module.exports = redis.createClient({URL}); +const client = redis.createClient({ + // url: URL, + socket: { host: REDIS_HOST, port: REDIS_PORT }, + password: REDIS_PASSWD, + // database: REDIS_DBNAME, + // legacyMode: true, // 语法向后(v3)部分兼容 +}); + +client.on("error", (err) => console.log(`redis client ~ Error ${err}`)); +client.on("connect", () => console.log("redis client ~ connect")); +client.on("reconnecting", () => console.log("redis client ~ reconnecting")); +client.on("ready", () => console.log("redis client ~ ready")); +client.on("end", () => console.log("redis client ~ end")); + +client.connect(); +client.ping(); +// try { +// } catch (error) { +// console.log(error); +// } + +module.exports = client; + +//* 方式二:redis 连接池 +//! unusable +/* var redisPool = require("redis-connection-pool")("myRedisPool", { + host: REDIS_HOST, // default + port: REDIS_PORT, //default + max_clients: 30, // defalut + perform_checks: false, // checks for needed push/pop functionality + database: 0, // database number to use + options: { + auth_pass: REDIS_PASSWD, + }, //options for createClient of node-redis, optional +}); +redisPool.set("test-key", "foobar", function (err) { + redisPool.get("test-key", function (err, reply) { + console.log(reply); // 'foobar' + }); +}); */ diff --git a/src/middlewares/auth.middleware.js b/src/middlewares/auth.middleware.js new file mode 100644 index 0000000..a65438b --- /dev/null +++ b/src/middlewares/auth.middleware.js @@ -0,0 +1,49 @@ +const jwt = require('jsonwebtoken') + +const { JWT_SECRET } = require('../config/config.default') + +const { + tokenExpiredError, + invalidToken, + hasNotAdminPermission, +} = require('../constant/error.type') + +const auth = async (ctx, next) => { + const { authorization = '' } = ctx.request.header + const token = authorization.replace('Bearer ', '') + // console.log(token) + + try { + // user中包含了payload的信息(id, user_name, is_admin) + const user = jwt.verify(token, JWT_SECRET) + ctx.state.user = user + } catch (err) { + switch (err.name) { + case 'TokenExpiredError': + console.error('token已过期', err) + return ctx.app.emit('error', tokenExpiredError, ctx) + case 'JsonWebTokenError': + console.error('无效的token', err) + return ctx.app.emit('error', invalidToken, ctx) + } + } + + await next() +} + +// 判断:是否有管理员权限 +const hadAdminPermission = async (ctx, next) => { + const { is_admin } = ctx.state.user + + if (!is_admin) { + console.error('该用户没有管理员的权限', ctx.state.user) + return ctx.app.emit('error', hasNotAdminPermission, ctx) + } + + await next() +} + +module.exports = { + auth, + hadAdminPermission, +} diff --git a/src/middlewares/user.middleware.js b/src/middlewares/user.middleware.js new file mode 100644 index 0000000..5674278 --- /dev/null +++ b/src/middlewares/user.middleware.js @@ -0,0 +1,92 @@ +const bcrypt = require('bcryptjs') + +const { getUerInfo } = require('../service/user.service') +const { + userFormateError, + userAlreadyExited, + userRegisterError, + userDoesNotExist, + userLoginError, + invalidPassword, +} = require('../constant/error.type') + +const userValidator = async (ctx, next) => { + const { user_name, password } = ctx.request.body + // 合法性 + if (!user_name || !password) { + console.error('用户名或密码为空', ctx.request.body) + ctx.app.emit('error', userFormateError, ctx) + return + } + + await next() +} + +const verifyUser = async (ctx, next) => { + const { user_name } = ctx.request.body + + // if (await getUerInfo({ user_name })) { + // ctx.app.emit('error', userAlreadyExited, ctx) + // return + // } + try { + const res = await getUerInfo({ user_name }) + + if (res) { + console.error('用户名已经存在', { user_name }) + ctx.app.emit('error', userAlreadyExited, ctx) + return + } + } catch (err) { + console.error('获取用户信息错误', err) + ctx.app.emit('error', userRegisterError, ctx) + return + } + + await next() +} + +const crpytPassword = async (ctx, next) => { + const { password } = ctx.request.body + + const salt = bcrypt.genSaltSync(10) + // hash保存的是 密文 + const hash = bcrypt.hashSync(password, salt) + + ctx.request.body.password = hash + + await next() +} + +const verifyLogin = async (ctx, next) => { + // 1. 判断用户是否存在(不存在:报错) + const { user_name, password } = ctx.request.body + + try { + const res = await getUerInfo({ user_name }) + + if (!res) { + console.error('用户名不存在', { user_name }) + ctx.app.emit('error', userDoesNotExist, ctx) + return + } + + // 2. 密码是否匹配(不匹配: 报错) + if (!bcrypt.compareSync(password, res.password)) { + ctx.app.emit('error', invalidPassword, ctx) + return + } + } catch (err) { + console.error(err) + return ctx.app.emit('error', userLoginError, ctx) + } + + await next() +} + +module.exports = { + userValidator, + verifyUser, + crpytPassword, + verifyLogin, +} diff --git a/src/models/user.js b/src/models/user.js new file mode 100644 index 0000000..393f8db --- /dev/null +++ b/src/models/user.js @@ -0,0 +1,30 @@ +const { DataTypes } = require('sequelize') + +const seq = require('../db/mariadb') + +// 创建模型(Model user -> 数据库表 users) +const User = seq.define('user', { + // id 会被sequelize自动创建, 管理 + username: { + type: DataTypes.STRING, + allowNull: false, + unique: true, + comment: '用户名, 唯一', + }, + password: { + type: DataTypes.CHAR(64), + allowNull: false, + comment: '密码', + }, + is_admin: { + type: DataTypes.BOOLEAN, + allowNull: false, + defaultValue: 0, + comment: '是否为管理员: 0 不是管理员(默认); 1 是管理员', + }, +}) + +// 强制同步数据库(创建数据表) +// User.sync({ force: true }) + +module.exports = User diff --git a/src/routers/user.js b/src/routers/user.js new file mode 100644 index 0000000..2bf1ec1 --- /dev/null +++ b/src/routers/user.js @@ -0,0 +1,4 @@ +const Router = require("koa-router"); +const router = new Router({ prefix: "/user" }); + +router.post("/list", getUserList); diff --git a/src/services/redis.js b/src/services/redis.js new file mode 100644 index 0000000..82a323d --- /dev/null +++ b/src/services/redis.js @@ -0,0 +1,29 @@ +const redisClient = require("../../db/redis"); + +/** +Object.prototype.toString = function () { + return JSON.stringify(this); +}; +*/ + +class RedisService { + client = redisClient; + + save2Redis(key, value) { + redisClient.set(key, value); + } + + getByKey(key, callback = null) { + if (callback) { + redisClient.get(key, callback); + } else { + redisClient.get(key, (err, value) => { + if (err) return null; + console.log(value); + // return value; + }); + } + } +} + +module.exports = new RedisService(); diff --git a/src/services/user.js b/src/services/user.js new file mode 100644 index 0000000..c96fd97 --- /dev/null +++ b/src/services/user.js @@ -0,0 +1,42 @@ +const User = require('../models/user') + +class UserService { + async createUser(user_name, password) { + // 插入数据 + // await表达式: promise对象的值 + const res = await User.create({ user_name, password }) + // console.log(res) + return res.dataValues + } + + async getUerInfo({ id, user_name, password, is_admin }) { + const whereOpt = {} + + id && Object.assign(whereOpt, { id }) + user_name && Object.assign(whereOpt, { user_name }) + password && Object.assign(whereOpt, { password }) + is_admin && Object.assign(whereOpt, { is_admin }) + + const res = await User.findOne({ + attributes: ['id', 'user_name', 'password', 'is_admin'], + where: whereOpt, + }) + + return res ? res.dataValues : null + } + + async updateById({ id, user_name, password, is_admin }) { + const whereOpt = { id } + const newUser = {} + + user_name && Object.assign(newUser, { user_name }) + password && Object.assign(newUser, { password }) + is_admin && Object.assign(newUser, { is_admin }) + + const res = await User.update(newUser, { where: whereOpt }) + // console.log(res) + return res[0] > 0 ? true : false + } +} + +module.exports = new UserService() diff --git a/src/util/bcrypt.js b/src/util/bcrypt.js new file mode 100644 index 0000000..c2e43e2 --- /dev/null +++ b/src/util/bcrypt.js @@ -0,0 +1,5 @@ +const bcryptjs = require("bcryptjs"); + +class Bcrypt {} + +module.exports = new Bcrypt(); diff --git a/src/views/.example b/src/views/.example new file mode 100644 index 0000000..e69de29 diff --git a/test/.example.redis.js b/test/.example.redis.js new file mode 100644 index 0000000..229bacd --- /dev/null +++ b/test/.example.redis.js @@ -0,0 +1,18 @@ +const { + client, + save2Redis, + getByKey, +} = require("../src/service/redis/base.service"); + +save2Redis("key1", "i am string value"); +// redis 储存时默认调用 Object.toString 而非 JSON.stringify +// save2Redis("key2", { key: "i am string value from Object.toString()" }); // value是object,报参数类型错误 + +getByKey("key2"); // 无console输出 + +client.rPush("alist", "abc"); +// client.rPush("alist", 123); // value是object,报参数类型错误 +client.lRange("alist", 0, -1, (err, v) => { + console.log(v); // 无console输出 + client.disconnect(); +});