学习教程第一步，文件夹里创建项目npm init -y 第二步，新建pages文件夹，新建index.js文件，文件内容如下：const Index = () => { return Welcome } export default Index 注意：key和secret在shopify partner页面里新建app后就会出现新建app时需要有一个ngrok的外网映射，需要做以下步骤：先安装ngroknpm i ngrok -g // 注意要全局安装 然后创建一个映射ngrok http 3000 就可以获得一个URL，把URL填到创建app时的App URL处，在Allowed redirection URL(s)填这个地址并且在后面跟/auth/callback第三步，安装react和相关依赖包npm i --save next react react-dom/server koa @shopify/koa-shopify-auth dotenv koa-router@^8.0.8 isomorphic-fetch 第四步，创建一个.env文件，内容如下：SHOPIFY_API_KEY='XXX' SHOPIFY_API_SECRET='XXX' SHOPIFY_API_SCOPES=read_products,write_products SHOPIFY_API_URL='https://8760-2409-8a00-2450-9940-69c5-bbfa-f871-8a2c.ngrok.io' 第五步，创建server.js文件，内容如下：require('isomorphic-fetch') const dotenv = require('dotenv') const Koa = require('koa') const next = require('next') const { default: createShopifyAuth } = require('@shopify/koa-shopify-auth') const { verifyRequest } = require('@shopify/koa-shopify-auth') const { default: Shopify, ApiVersion } = require('@shopify/shopify-api') const Router = require('koa-router') dotenv.config() Shopify.Context.initialize({ API_KEY: process.env.SHOPIFY_API_KEY, API_SECRET_KEY: process.env.SHOPIFY_API_SECRET, SCOPES: process.env.SHOPIFY_API_SCOPES.split(","), HOST_NAME: process.env.SHOPIFY_API_URL.replace(/https:\/\//, ""), API_VERSION: ApiVersion.October20, IS_EMBEDDED_APP: true, SESSION_STORAGE: new Shopify.Session.MemorySessionStorage() }) const port = parseInt(process.env.PORT, 10) || 3000 const dev = process.env.NODE_ENV !== 'production' const app = next({ dev }) const handle = app.getRequestHandler() const ACTIVE_SHOPIFY_SHOPS = {} app.prepare().then(() => { const server = new Koa() const router = new Router() server.keys = [Shopify.Context.API_SECRET_KEY] server.use( createShopifyAuth({ afterAuth(ctx) { const { shop, scope } = ctx.state.shopify ACTIVE_SHOPIFY_SHOPS[shop] = scope if ( ACTIVE_SHOPIFY_SHOPS[shop] ) { ctx.redirect(`https://${shop}/admin/apps`) } else { ctx.redirect(`/?shop=${shop}`) } } }) ) const handleRequest = async (ctx) => { await handle(ctx.req, ctx.res) ctx.respond = false ctx.res.statusCode = 200 } router.get("/", async (ctx) => { const shop = ctx.query.shop if (ACTIVE_SHOPIFY_SHOPS[shop] === undefined) { ctx.redirect(`/auth?shop=${shop}`) } else { await handleRequest(ctx) } }) router.get("(.*)", handleRequest) router.get("(/_next/static/.*)", handleRequest) router.get("/_next/webpack-hmr", handleRequest) router.get("(.*)", verifyRequest(), handleRequest) server.use(router.allowedMethods()) server.use(router.routes()) server.listen(port, () => { console.log(`> Ready on http://localhost:${port}`) }) }) 第六步，修改package.json文件 "scripts": { "test": "echo \"Error: no test specified\" && exit 1", "dev": "node server.js", "build": "next build", "start": "NODE_ENV=production node server.js" }, 第七步，运行npm run dev 第八步，在app的页面里，有一个test your app，点击select store，然后在商店名称的右边点击install app就可以看到你的index.js里的return的内容了。总结：视频教程里，index.js里没有写return，会导致以下错误：Error: Index(...): Nothing was returned from render. This usually means a return statement is missing. Or, to render nothing, return null.ngrok内网穿透反应有点慢server.js里的内容比较复杂，大概就是获取授权的事情，不知道vue能不能做到这些东西，其实用shopify-cli一键生成就可以了，之所以跟着follow一次，是希望大概知道是怎么回事
node js的npm包shopify-node-apishopify API list
学习视频三个文件：server.js定义api服务，models.js定义模型数据库操作，test.http是REST CLIENT插件的文件server.js const { User } = require('./models') const express = require('express') const jwt = require('jsonwebtoken') const app = express() const SECRET = 'fdafdasfasdofdasa' app.use(express.json()) // User.db.dropDatabase() // 获取所有用户 app.get('/api/users', async (req, res) => { const users = await User.find() res.send(users) }) // 用户注册返回注册信息 app.post('/api/register', async (req, res) => { const user = await User.create({ username: req.body.username, password: req.body.password }) res.send(user) }) // 用户登录返回token app.post('/api/login', async (req, res) => { const user = await User.findOne({ username: req.body.username }) if (!user) { return res.status(422).send({ message: '用户名不存在' }) } const isPasswordValid = require('bcrypt').compareSync( req.body.password, user.password ) if (!isPasswordValid) { return res.status(422).send({ message: '密码无效' }) } // 生成token const token = jwt.sign({ id: String(user._id) }, SECRET) res.send({ user, token }) }) // 验证token的中间件 const auth = async (req, res, next) => { const raw = String(req.headers.authorization).split(' ').pop() const { id } = jwt.verify(raw, SECRET) req.user = await User.findById(id) next() } // 获取用户信息 app.get('/api/profile', auth, async (req, res) => { res.send(req.user) }) // 获取用户的订单 app.get('/api/orders', auth, async (req, res) => { const orders = await Order.find().where({ user: req.user }) res.send(orders) }) app.listen(3001, () => { console.log('http://localhost:3001') }) models.jsconst mongoose = require('mongoose') mongoose.connect('mongodb://localhost:27017/express-auth', { useNewUrlParser: true }) const UserSchema = new mongoose.Schema({ username: { type: String, unique: true}, password: { type: String, set ( val ) { // 给密码加密 return require('bcrypt').hashSync(val, 10) } } }) const User = mongoose.model('User', UserSchema) module.exports = { User } test.http@url=http://localhost:3001/api @json=Content-Type: application/json ### 所有用户 get {{url}}/users ### 注册 post {{url}}/register {{json}} { "username": "user3", "password": "123456" } ### 登录 post {{url}}/login {{json}} { "username": "user3", "password": "123456" } ### 个人信息 get {{url}}/profile Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjYxM2UxOTJlNDg4MzRkYzhkNTE4NWYzNyIsImlhdCI6MTYzMTQ2MDUwOH0.Jat8OBXwnGRBTtgLoJb0t-VzZAoRxYw9_6hrffIcNxU
学习资源整体思路：login的时候，生成并返回token给前端前端向后端发送请求时，在header中带上authorization: token在请求中，使用jwt.verfiy去验证token的有效性，决定是否允许访问资源当前问题：实际应用中，每次都需要使用user数据来验证token吗？user数据存储在哪里？每次从数据库里调用是不是有点影响性能呀？答案：我仔细看了一下，好像验证token的时候，只需要token和secretKey就可以了，完全不用重新提取一遍用户数据，这可能厉害了，也就是说加密的时候已经把用户数据存在token里了？完整代码：const express = require('express') const jwt = require('jsonwebtoken') const app = express() //登录接口，获取Token app.post('/api/login', (req, res) => { // 用户信息，问题：这个信息mongodb来？需要存起来吗？ const user = { id: 1, username: 'Alex', email: 'alex@qq.com' } // 使用user信息生成token，返回前端 jwt.sign({user}, 'secretkey', { expiresIn: '30s' }, (err, token) => { res.json({ token }) }) }) // 需要token保护的接口，使用verfiyToken中间件验证请求是否带有token app.post('/api/posts', verifyToken, (req, res) => { // 使用jwt验证token是否正确 jwt.verify(req.token, 'secretkey', (err, authData) => { if (err) res.sendStatus(403) res.json({ message: 'Post created...', authData }) }) }) // 验证请求有无token，没有就返回403，有的话就获取token，放到req.token里 function verifyToken (req, res, next) { // 获取headers里的authorization数据 const bearerHeader = req.headers['authorization'] // 如果没有authorization数据，返回403 if (typeof bearerHeader === 'undefined') res.sendStatus(403) // 如果有，获取token，然后继续执行 const bearer = bearerHeader.split(' ') req.token = bearer[1] next() } app.listen('5000', () => console.log('server is listening on port 5000'))
前后端分离token登录的思路：1、首次登录时，后端服务器判断用户账号密码正确之后，根据用户id、用户名、定义好的秘钥、过期时间 生成 token ，返回给前端2、前端拿到后端返回的 token ,存储在 localStroage 里3、前端每次路由跳转， 判断 localStroage 有无 token ，没有则跳转到登录页，有则请求获取用户信息，改变登录状态4、每次请求接口，在 请求头里携带 token5、后端接口 判断 请求头有无 token，没有或者 token 过期，返回4016、前端得到 401 状态码，重定向 到登录页面本文来自CSDN
本文我们要封装一个数据库连接方法，最开始这个方法是写在posts.js里的，现在要把这个方法写到dbconfig.js里，以便其它接口文件也可以进行调用。原始的posts.js代码如下：const express = require('express') const mongodb = require('mongodb') const router = express.Router() // 连接数据库并指定要操作的collection async function loadPostsCollection(){ const client = await mongodb.MongoClient.connect ('mongodb://localhost:27017', { useUnifiedTopology: true }) return client.db('dbname').collection('posts') } // 查询数据并发送给前端 router.get('/', async (req, res) => { const posts = await loadPostsCollection() res.send(await posts.find({}).toArray()) }) module.exports = router 之后我们要把loadPostsCollection()这个方法封装到../utils/dbconfig里，把这部分代码复制到文件里，并改成以下这样：const mongodb = require('mongodb') // 将'users'改成collectionName作为参数传进来 module.exports = async (collectionName) => { const client = await mongodb.MongoClient.connect ('mongodb://localhost:27017', { useUnifiedTopology: true }) return client.db('dbname').collection(collectionName) } 回到调用该方法的文件，将dbconfig作为一个方法引入进来。const loadCollection = require('../utils/dbconfig') 在使用的时候，首先定义好collectionName，之后可直接使用封装的方法。// 查询数据 const collectionName = 'posts' router.get('/', async (req, res) => { const posts = await loadCollection(collectionName) res.send(await posts.find({}).toArray()) }) 修改后的posts.js如下：const express = require('express') const loadCollection = require('../utils/dbconfig') const router = express.Router() const collectionName = 'posts' // 查询数据 router.get('/', async (req, res) => { const posts = await loadCollection(collectionName) res.send(await posts.find({}).toArray()) }) module.exports = router 完！这个是其中一种封装方法，其他的方法查看这里。
一般node都跟mongodb等nosql数据库连接，不过转型期间很多时候我们还得使用mysql数据库，以下是Express连接mysql数据库的方法。第一步：引入mysql包，然后新建一个dbconfig.js文件，内容如下：const mysql = require('mysql') module.exports = { sqlConnect: (sql, sqlArr, callBack) => { const pool = mysql.createPool({ connectionLimit: 10, host: 'localhost', database: 'dbname', user: 'username', password: 'password' }) pool.getConnection((err, conn) => { if (err) return console.log('连接失败') conn.query(sql, sqlArr, callBack) conn.release() }) } } 第二步：在需要使用数据库的地方，比如routes下面的文件，引入dbconfig.js，并准备相关参数，调用db.sqlConnect()方法来连接数据库即可。const express = require('express') const db = require('../utils/dbconfig') const router = express.Router() router.get('/', (req, res, next) => { const sql = 'SELECT * FROM user' const sqlArr = [] const callBack = (error, results, fields) => { if (error) throw error res.status(200).send(results) } db.sqlConnect(sql, sqlArr, callBack) }) module.exports = router 完结！
这两天在研究express里连接mysql，在创建数据库连接的时候，发现下面这个奇怪的问题。这面这部分代码是这样的：const mysql = require('mysql') module.exports = { // database config config: { connectionLimit: 10, host: '', database: '', user: '', password: '' }, sqlConnect: (sql, sqlArr, callBack) => { const pool = mysql.createPool(this.config) pool.getConnection((err, conn) => { if (err) return console.log('连接失败') conn.query(sql, sqlArr, callBack) conn.release() }) } } 执行的时候却报错了：TypeError: Cannot read property 'acquireTimeout' of undefined 但是如果把config直接写到mysql.createPool里，居然就没有问题。const mysql = require('mysql') module.exports = { sqlConnect: (sql, sqlArr, callBack) => { const pool = mysql.createPool({ connectionLimit: 10, host: '', database: '', user: '', password: '' }) pool.getConnection((err, conn) => { if (err) return console.log('连接失败') conn.query(sql, sqlArr, callBack) conn.release() }) } } 真是奇怪，难道我使用config来创建的话，会自动要求有一个acquireTimeout的参数？