diff --git a/miniprogram/app.js b/miniprogram/app.js index 782f46e..f2297a3 100644 --- a/miniprogram/app.js +++ b/miniprogram/app.js @@ -26,9 +26,10 @@ App({ loadProfile: function () { var self = this - http.get('/api/v1/user/profile').then(function (profile) { + return http.get('/api/v1/user/profile').then(function (profile) { self.globalData.userInfo = profile self.globalData.userId = String(profile.user_id) + return profile }).catch(function () { wx.removeStorageSync('token') }) diff --git a/miniprogram/pages/login/login.js b/miniprogram/pages/login/login.js index 129f0c3..3f27340 100644 --- a/miniprogram/pages/login/login.js +++ b/miniprogram/pages/login/login.js @@ -16,7 +16,7 @@ Page({ app.doLogin().then(function (data) { self.setData({ loading: false }) - if (data && data.is_new_user) { + if (data && (data.is_new_user || !data.user_info || !data.user_info.phone)) { wx.redirectTo({ url: '/pages/register/register' }) } else { wx.switchTab({ url: '/pages/index/index' }) diff --git a/miniprogram/pages/register/register.js b/miniprogram/pages/register/register.js index 2d538c7..4bae382 100644 --- a/miniprogram/pages/register/register.js +++ b/miniprogram/pages/register/register.js @@ -1,5 +1,6 @@ var app = getApp() var http = require('../../utils/request') +var config = require('../../config/env') var DEFAULT_AVATAR = 'https://mmbiz.qpic.cn/mmbiz/icTdbqWNOwNRna42FI9t2NHfLvvMiaiaCJQn3KP2P0vAHp1QBP0piavhGfhKCARlGd6z1EbR58ibJXKCp5WVlNnojwA/640' @@ -65,6 +66,31 @@ Page({ }) }, + uploadAvatar: function (tempPath) { + return new Promise(function (resolve, reject) { + var token = wx.getStorageSync('token') + wx.uploadFile({ + url: config.API_BASE + '/api/v1/user/avatar', + filePath: tempPath, + name: 'file', + header: { Authorization: 'Bearer ' + token }, + success: function (res) { + try { + var data = JSON.parse(res.data) + if (data.code === 0 && data.data && data.data.avatar) { + resolve(data.data.avatar) + } else { + reject(new Error(data.message || '上传失败')) + } + } catch (e) { + reject(new Error('上传失败')) + } + }, + fail: function () { reject(new Error('上传失败')) } + }) + }) + }, + onSubmit: function () { var self = this var nickname = (self.data.nickname || '').trim() @@ -82,9 +108,15 @@ Page({ self.setData({ loading: true }) - http.put('/api/v1/user/profile', { - nickname: nickname, - avatar: avatarUrl !== DEFAULT_AVATAR ? avatarUrl : '' + var avatarPromise = (avatarUrl && avatarUrl !== DEFAULT_AVATAR) + ? self.uploadAvatar(avatarUrl) + : Promise.resolve('') + + avatarPromise.then(function (permanentUrl) { + return http.put('/api/v1/user/profile', { + nickname: nickname, + avatar: permanentUrl || '' + }) }).then(function () { return app.loadProfile() }).then(function () { diff --git a/server/package-lock.json b/server/package-lock.json index f1b2255..a0771a6 100644 --- a/server/package-lock.json +++ b/server/package-lock.json @@ -14,6 +14,7 @@ "express": "^5.2.1", "express-rate-limit": "^8.5.0", "jsonwebtoken": "^9.0.2", + "multer": "^2.1.1", "mysql2": "^3.11.3" } }, @@ -98,6 +99,12 @@ } } }, + "node_modules/append-field": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/append-field/-/append-field-1.0.0.tgz", + "integrity": "sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw==", + "license": "MIT" + }, "node_modules/asn1": { "version": "0.2.6", "resolved": "https://registry.npmmirror.com/asn1/-/asn1-0.2.6.tgz", @@ -215,6 +222,23 @@ "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", "license": "BSD-3-Clause" }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "license": "MIT" + }, + "node_modules/busboy": { + "version": "1.6.0", + "resolved": "https://registry.npmmirror.com/busboy/-/busboy-1.6.0.tgz", + "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==", + "dependencies": { + "streamsearch": "^1.1.0" + }, + "engines": { + "node": ">=10.16.0" + } + }, "node_modules/bytes": { "version": "3.1.2", "resolved": "https://registry.npmmirror.com/bytes/-/bytes-3.1.2.tgz", @@ -271,6 +295,21 @@ "node": ">= 0.8" } }, + "node_modules/concat-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/concat-stream/-/concat-stream-2.0.0.tgz", + "integrity": "sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==", + "engines": [ + "node >= 6.0" + ], + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.0.2", + "typedarray": "^0.0.6" + } + }, "node_modules/conf": { "version": "9.0.2", "resolved": "https://registry.npmmirror.com/conf/-/conf-9.0.2.tgz", @@ -1288,6 +1327,47 @@ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "license": "MIT" }, + "node_modules/multer": { + "version": "2.1.1", + "resolved": "https://registry.npmmirror.com/multer/-/multer-2.1.1.tgz", + "integrity": "sha512-mo+QTzKlx8R7E5ylSXxWzGoXoZbOsRMpyitcht8By2KHvMbf3tjwosZ/Mu/XYU6UuJ3VZnODIrak5ZrPiPyB6A==", + "license": "MIT", + "dependencies": { + "append-field": "^1.0.0", + "busboy": "^1.6.0", + "concat-stream": "^2.0.0", + "type-is": "^1.6.18" + }, + "engines": { + "node": ">= 10.16.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/multer/node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmmirror.com/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/multer/node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmmirror.com/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "license": "MIT", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/mysql2": { "version": "3.22.3", "resolved": "https://registry.npmmirror.com/mysql2/-/mysql2-3.22.3.tgz", @@ -1546,6 +1626,20 @@ "node": ">= 0.10" } }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmmirror.com/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/request": { "version": "2.88.2", "resolved": "https://registry.npmmirror.com/request/-/request-2.88.2.tgz", @@ -1838,6 +1932,23 @@ "node": ">= 0.8" } }, + "node_modules/streamsearch": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/streamsearch/-/streamsearch-1.1.0.tgz", + "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmmirror.com/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, "node_modules/strnum": { "version": "1.1.2", "resolved": "https://registry.npmmirror.com/strnum/-/strnum-1.1.2.tgz", @@ -1929,6 +2040,12 @@ "url": "https://opencollective.com/express" } }, + "node_modules/typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmmirror.com/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==", + "license": "MIT" + }, "node_modules/undici-types": { "version": "7.19.2", "resolved": "https://registry.npmmirror.com/undici-types/-/undici-types-7.19.2.tgz", @@ -1954,6 +2071,12 @@ "punycode": "^2.1.0" } }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "license": "MIT" + }, "node_modules/uuid": { "version": "3.4.0", "resolved": "https://registry.npmmirror.com/uuid/-/uuid-3.4.0.tgz", diff --git a/server/package.json b/server/package.json index 4d60469..93f708b 100644 --- a/server/package.json +++ b/server/package.json @@ -15,6 +15,7 @@ "express": "^5.2.1", "express-rate-limit": "^8.5.0", "jsonwebtoken": "^9.0.2", + "multer": "^2.1.1", "mysql2": "^3.11.3" } } diff --git a/server/src/routes/user.js b/server/src/routes/user.js index 6a87186..0163a58 100644 --- a/server/src/routes/user.js +++ b/server/src/routes/user.js @@ -1,11 +1,15 @@ const router = require('express').Router() +const multer = require('multer') +const COS = require('cos-nodejs-sdk-v5') const { ok, fail } = require('../lib/response') const { requireUser } = require('../middleware/auth') const { getPhoneNumber } = require('../lib/wechat') +const config = require('../config') const userDao = require('../dao/user.dao') const deviceDao = require('../dao/device.dao') const logDao = require('../dao/log.dao') +const upload = multer({ storage: multer.memoryStorage(), limits: { fileSize: 2 * 1024 * 1024 } }) const wrap = fn => (req, res, next) => fn(req, res, next).catch(next) router.get('/user/profile', requireUser, wrap(async (req, res) => { @@ -32,6 +36,25 @@ router.put('/user/profile', requireUser, wrap(async (req, res) => { res.json(ok({ message: 'success' })) })) +router.post('/user/avatar', requireUser, upload.single('file'), wrap(async (req, res) => { + if (!req.file) return res.json(fail(2001, 'file required')) + const ext = (req.file.originalname || '').split('.').pop() || 'jpg' + const key = 'avatars/' + req.user.user_id + '_' + Date.now() + '.' + ext + const cos = new COS({ SecretId: config.cos.secretId, SecretKey: config.cos.secretKey }) + const result = await new Promise((resolve, reject) => { + cos.putObject({ + Bucket: config.cos.bucket, + Region: config.cos.region, + Key: key, + Body: req.file.buffer, + ContentType: req.file.mimetype + }, (err, data) => err ? reject(err) : resolve(data)) + }) + const avatarUrl = 'https://' + config.cos.bucket + '.cos.' + config.cos.region + '.myqcloud.com/' + key + await userDao.updateProfile(req.user.user_id, { nickname: null, avatar: avatarUrl, gender: null }) + res.json(ok({ avatar: avatarUrl })) +})) + router.post('/user/phone', requireUser, wrap(async (req, res) => { const code = String(req.body.code || '').trim() if (!code) return res.json(fail(2001, 'phone code required'))