├── app ├── app.js ├── app.wxss ├── images │ ├── logo.png │ ├── qr.png │ └── camera.png ├── config.js ├── pages │ ├── index │ │ ├── index.json │ │ ├── index.js │ │ ├── index.wxml │ │ └── index.wxss │ └── album │ │ ├── album.wxss │ │ ├── album.wxml │ │ └── album.js ├── lib │ ├── api.js │ ├── request.js │ └── util.js └── app.json ├── server ├── globals.js ├── routes │ ├── index.js │ └── album │ │ ├── routehub.js │ │ └── handlers │ │ ├── list.js │ │ ├── delete.js │ │ └── upload.js ├── process.json ├── services │ └── cos │ │ └── index.js ├── common │ └── routerbase.js ├── package.json ├── config.js ├── app.js └── middlewares │ └── route_dispatcher.js ├── .gitignore ├── screenshot.png ├── LICENSE └── README.md /app/app.js: -------------------------------------------------------------------------------- 1 | App({ 2 | 3 | }); -------------------------------------------------------------------------------- /server/globals.js: -------------------------------------------------------------------------------- 1 | global.SERVER_ROOT = __dirname; -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | npm-debug.log 3 | tmp -------------------------------------------------------------------------------- /app/app.wxss: -------------------------------------------------------------------------------- 1 | page { 2 | background-color: #1c1b16; 3 | } -------------------------------------------------------------------------------- /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CFETeam/weapp-demo-album/HEAD/screenshot.png -------------------------------------------------------------------------------- /server/routes/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | '/album': 'album/routehub', 3 | }; 4 | -------------------------------------------------------------------------------- /app/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CFETeam/weapp-demo-album/HEAD/app/images/logo.png -------------------------------------------------------------------------------- /app/images/qr.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CFETeam/weapp-demo-album/HEAD/app/images/qr.png -------------------------------------------------------------------------------- /app/images/camera.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CFETeam/weapp-demo-album/HEAD/app/images/camera.png -------------------------------------------------------------------------------- /app/config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | /** 通讯域名 */ 3 | host: 'www.qcloud.la', 4 | basePath: '/applet/album', 5 | }; -------------------------------------------------------------------------------- /app/pages/index/index.json: -------------------------------------------------------------------------------- 1 | { 2 | "navigationBarBackgroundColor": "#2277da", 3 | "navigationBarTextStyle": "white" 4 | } -------------------------------------------------------------------------------- /app/pages/index/index.js: -------------------------------------------------------------------------------- 1 | Page({ 2 | // 前往相册页 3 | gotoAlbum() { 4 | wx.navigateTo({ url: '../album/album' }); 5 | }, 6 | }); 7 | -------------------------------------------------------------------------------- /app/lib/api.js: -------------------------------------------------------------------------------- 1 | var config = require('../config.js'); 2 | 3 | module.exports = { 4 | getUrl(route) { 5 | return `https://${config.host}${config.basePath}${route}`; 6 | }, 7 | }; -------------------------------------------------------------------------------- /server/process.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "album", 3 | "script": "app.js", 4 | "cwd": "./", 5 | "exec_mode": "fork", 6 | "watch": true, 7 | "ignore_watch": ["tmp"], 8 | "env": { 9 | "NODE_ENV": "production" 10 | } 11 | } -------------------------------------------------------------------------------- /server/routes/album/routehub.js: -------------------------------------------------------------------------------- 1 | module.exports = (router) => { 2 | // 获取图片列表 3 | router.get('/list', require('./handlers/list')); 4 | 5 | // 上传图片 6 | router.post('/upload', require('./handlers/upload')); 7 | 8 | // 删除图片 9 | router.post('/delete', require('./handlers/delete')); 10 | }; -------------------------------------------------------------------------------- /app/app.json: -------------------------------------------------------------------------------- 1 | { 2 | "pages": [ 3 | "pages/index/index", 4 | "pages/album/album" 5 | ], 6 | 7 | "window": { 8 | "backgroundTextStyle": "dark", 9 | "navigationBarBackgroundColor": "#373b3e", 10 | "navigationBarTitleText": "相册回收站", 11 | "windowBackground": "#1c1b16", 12 | "navigationBarTextStyle": "white" 13 | } 14 | } -------------------------------------------------------------------------------- /server/services/cos/index.js: -------------------------------------------------------------------------------- 1 | const COS = require('cos-nodejs-sdk-v5') 2 | const _cosConfig = require('cos-nodejs-sdk-v5/sdk/config') 3 | const config = require('../../config') 4 | 5 | /** 6 | * init COS config 7 | * see: https://github.com/tencentyun/cos-nodejs-sdk-v5/blob/master/sdk/config.js#L24 8 | */ 9 | _cosConfig.setAppInfo(config.cosAppId, config.cosSecretId, config.cosSecretKey); 10 | 11 | module.exports = COS 12 | -------------------------------------------------------------------------------- /app/lib/request.js: -------------------------------------------------------------------------------- 1 | module.exports = (options) => { 2 | return new Promise((resolve, reject) => { 3 | options = Object.assign(options, { 4 | success(result) { 5 | if (result.statusCode === 200) { 6 | resolve(result.data); 7 | } else { 8 | reject(result); 9 | } 10 | }, 11 | 12 | fail: reject, 13 | }); 14 | 15 | wx.request(options); 16 | }); 17 | }; -------------------------------------------------------------------------------- /app/pages/index/index.wxml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 恭喜你 4 | 成功地搭建了一个微信小程序 5 | 6 | 7 | 8 | 9 | 10 | 分享二维码邀请好友结伴一起写小程序! 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /app/lib/util.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | // 一维数组转二维数组 3 | listToMatrix(list, elementsPerSubArray) { 4 | let matrix = [], i, k; 5 | 6 | for (i = 0, k = -1; i < list.length; i += 1) { 7 | if (i % elementsPerSubArray === 0) { 8 | k += 1; 9 | matrix[k] = []; 10 | } 11 | 12 | matrix[k].push(list[i]); 13 | } 14 | 15 | return matrix; 16 | }, 17 | 18 | // 为promise设置简单回调(无论成功或失败都执行) 19 | always(promise, callback) { 20 | promise.then(callback, callback); 21 | return promise; 22 | }, 23 | }; -------------------------------------------------------------------------------- /server/common/routerbase.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 封装的路由公共基类,用于添加公用方法,不能直接实例化 3 | */ 4 | 5 | class RouterBase { 6 | constructor(req, res, next) { 7 | Object.assign(this, { req, res, next }); 8 | } 9 | 10 | /** 11 | * 静态工厂方法:创建用以响应路由的回调函数 12 | */ 13 | static makeRouteHandler() { 14 | return (req, res, next) => new this(req, res, next).handle(); 15 | } 16 | 17 | /** 18 | * 子类实现该方法处理请求 19 | */ 20 | handle() { 21 | throw new Error(`Please implement instance method \`${this.constructor.name}::handle\`.`); 22 | } 23 | } 24 | 25 | module.exports = RouterBase; -------------------------------------------------------------------------------- /server/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "album-server", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "start-dev": "nodemon app.js", 8 | "start": "pm2 start process.json" 9 | }, 10 | "keywords": [], 11 | "author": "", 12 | "license": "ISC", 13 | "dependencies": { 14 | "body-parser": "^1.15.2", 15 | "cos-nodejs-sdk-v5": "^1.0.0", 16 | "express": "^4.14.0", 17 | "file-type": "^3.8.0", 18 | "lodash": "^4.16.1", 19 | "morgan": "^1.7.0", 20 | "multiparty": "^4.1.2", 21 | "qcloud_cos": "^1.0.6", 22 | "read-chunk": "^2.0.0", 23 | "shortid": "^2.2.6" 24 | }, 25 | "devDependencies": { 26 | "nodemon": "^1.10.2" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /server/config.js: -------------------------------------------------------------------------------- 1 | const CONF = { 2 | port: '9993', 3 | ROUTE_BASE_PATH: '/applet', 4 | 5 | /** 6 | * COS 信息配置 7 | * 查看:https://console.qcloud.com/capi 8 | */ 9 | 10 | // APPID 11 | cosAppId: '', 12 | /** 13 | * 区域 14 | * 华北:cn-north 15 | * 华东:cn-east 16 | * 华南:cn-south 17 | * 西南:cn-southwest 18 | */ 19 | cosRegion: 'cn-north', 20 | // SecretId 21 | cosSecretId: '', 22 | // SecretKey 23 | cosSecretKey: '', 24 | // Bucket 名称 25 | cosFileBucket: '', 26 | // 文件夹 27 | cosUploadFolder: '/' 28 | } 29 | 30 | // 生成访问 COS 的域名,无需修改 31 | CONF.cosDomain = (() => `http://${CONF.cosFileBucket}-${CONF.cosAppId}.costj.myqcloud.com/`)() 32 | 33 | module.exports = CONF 34 | -------------------------------------------------------------------------------- /server/app.js: -------------------------------------------------------------------------------- 1 | require('./globals'); 2 | 3 | const http = require('http'); 4 | const express = require('express'); 5 | const bodyParser = require('body-parser'); 6 | const morgan = require('morgan'); 7 | const config = require('./config'); 8 | 9 | const app = express(); 10 | 11 | app.set('query parser', 'simple'); 12 | app.set('case sensitive routing', true); 13 | app.set('jsonp callback name', 'callback'); 14 | app.set('strict routing', true); 15 | app.set('trust proxy', true); 16 | 17 | app.disable('x-powered-by'); 18 | 19 | // 记录请求日志 20 | app.use(morgan('tiny')); 21 | 22 | // parse `application/x-www-form-urlencoded` 23 | app.use(bodyParser.urlencoded({ extended: true })); 24 | 25 | // parse `application/json` 26 | app.use(bodyParser.json()); 27 | 28 | app.use(require('./middlewares/route_dispatcher')); 29 | 30 | // 打印异常日志 31 | process.on('uncaughtException', error => { 32 | console.log(error); 33 | }); 34 | 35 | // 启动server 36 | http.createServer(app).listen(config.port, () => { 37 | console.log('Express server listening on port: %s', config.port); 38 | }); 39 | -------------------------------------------------------------------------------- /server/middlewares/route_dispatcher.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 通用路由分发器 3 | */ 4 | 5 | const express = require('express'); 6 | const path = require('path'); 7 | const _ = require('lodash'); 8 | const config = require('../config'); 9 | const routes = require('../routes'); 10 | 11 | const routeOptions = { 'caseSensitive': true, 'strict': true }; 12 | const routeDispatcher = express.Router(routeOptions); 13 | 14 | _.each(routes, (route, subpath) => { 15 | const router = express.Router(routeOptions); 16 | 17 | let routePath; 18 | 19 | // ignore `config.ROUTE_BASE_PATH` if `subpath` begin with `~` 20 | if (subpath[0] === '~') { 21 | routePath = subpath.slice(1); 22 | } else { 23 | routePath = config.ROUTE_BASE_PATH + subpath; 24 | } 25 | 26 | require(path.join(global.SERVER_ROOT, 'routes', route))(router); 27 | 28 | routeDispatcher.use(routePath, router, (err, req, res, next) => { 29 | // mute `URIError` error 30 | if (err instanceof URIError) { 31 | return next(); 32 | } 33 | 34 | throw err; 35 | }); 36 | }); 37 | 38 | module.exports = routeDispatcher; -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | LICENSE - "MIT License" 2 | 3 | Copyright (c) 2016 by Tencent Cloud 4 | 5 | Permission is hereby granted, free of charge, to any person 6 | obtaining a copy of this software and associated documentation 7 | files (the "Software"), to deal in the Software without 8 | restriction, including without limitation the rights to use, 9 | copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the 11 | Software is furnished to do so, subject to the following 12 | conditions: 13 | 14 | The above copyright notice and this permission notice shall be 15 | included in all copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 18 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 19 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 20 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 21 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 22 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 23 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 24 | OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /server/routes/album/handlers/list.js: -------------------------------------------------------------------------------- 1 | const _ = require('lodash'); 2 | const path = require('path'); 3 | const RouterBase = require('../../../common/routerbase'); 4 | const config = require('../../../config'); 5 | const cos = require('../../../services/cos'); 6 | 7 | class ListImages extends RouterBase { 8 | handle() { 9 | const cosParams = { 10 | Bucket : config.cosFileBucket, 11 | Region : config.cosRegion, 12 | MaxKeys : 100 13 | } 14 | 15 | cos.getBucket(cosParams, (err, res) => { 16 | 17 | if (err) { 18 | this.res.json({ code: -1, msg: 'failed', data: {} }); 19 | return; 20 | } 21 | 22 | this.res.json({ 23 | code: 0, 24 | msg: 'ok', 25 | data: _.map(res.Contents, 'Key').filter(item => { 26 | let extname = String(path.extname(item)).toLowerCase(); 27 | 28 | // 只返回`jpg/png`后缀图片 29 | return ['.jpg', '.png'].includes(extname) 30 | }).map(v => config.cosDomain + v), 31 | }); 32 | }); 33 | } 34 | } 35 | 36 | module.exports = ListImages.makeRouteHandler(); -------------------------------------------------------------------------------- /app/pages/album/album.wxss: -------------------------------------------------------------------------------- 1 | .container { 2 | width: 100%; 3 | height: 100%; 4 | } 5 | 6 | .album-container { 7 | margin: 0.1rem 0; 8 | } 9 | 10 | .item-group { 11 | display: flex; 12 | } 13 | 14 | .album-item { 15 | flex: 1; 16 | margin: 0.1rem; 17 | background: #333; 18 | text-align: center; 19 | height: 6.66rem; 20 | line-height: 6.66rem; 21 | } 22 | 23 | .album-item.empty { 24 | background: transparent; 25 | } 26 | 27 | .upload-image { 28 | color: #ccc; 29 | background: #333; 30 | position: absolute; 31 | left: 0.1rem; 32 | top: 0.2rem; 33 | width: 6.46rem; 34 | height: 6.66rem; 35 | text-align: center; 36 | line-height: 6.66rem; 37 | } 38 | 39 | .upload-image image { 40 | position: absolute; 41 | left: 0; 42 | width: 100%; 43 | height: 2.6rem; 44 | top: 1.2rem; 45 | } 46 | 47 | .upload-image text { 48 | position: absolute; 49 | width: 100%; 50 | height: 2rem; 51 | left: 0; 52 | top: 1.6rem; 53 | } 54 | 55 | .swiper-container { 56 | position: fixed; 57 | left: 0; 58 | top: 0; 59 | width: 100%; 60 | height: 100%; 61 | background: #000; 62 | } 63 | 64 | .swiper-container image { 65 | width: 100%; 66 | height: 100%; 67 | } 68 | 69 | action-sheet-item.warn { 70 | color: #e64340; 71 | } -------------------------------------------------------------------------------- /server/routes/album/handlers/delete.js: -------------------------------------------------------------------------------- 1 | const _ = require('lodash'); 2 | const RouterBase = require('../../../common/routerbase'); 3 | const config = require('../../../config'); 4 | const cos = require('../../../services/cos'); 5 | 6 | class DeleteImage extends RouterBase { 7 | handle() { 8 | let filePath = String(this.req.body.filepath || ''); 9 | 10 | try { 11 | filePath = decodeURIComponent(filePath); 12 | 13 | if (!filePath.startsWith(config.cosUploadFolder)) { 14 | throw new Error('operation forbidden'); 15 | } 16 | 17 | } catch (err) { 18 | return this.res.json({ 19 | code: -1, 20 | msg: 'failed', 21 | data: _.pick(err, ['name', 'message']), 22 | }); 23 | } 24 | 25 | filePath = filePath.slice(1) 26 | 27 | const params = { 28 | Bucket: config.cosFileBucket, 29 | Region: config.cosRegion, 30 | Key: filePath 31 | } 32 | 33 | cos.deleteObject(params, (err, data) => { 34 | if (err) { 35 | this.res.json({ 36 | code: -1, 37 | msg: 'failed', 38 | data: _.pick(err, ['name', 'message']), 39 | }) 40 | } else { 41 | this.res.json({ 42 | code: 0, 43 | msg: 'ok', 44 | data: {}, 45 | }) 46 | } 47 | }) 48 | } 49 | } 50 | 51 | module.exports = DeleteImage.makeRouteHandler(); -------------------------------------------------------------------------------- /app/pages/album/album.wxml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 上传图片 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /server/routes/album/handlers/upload.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const path = require('path'); 3 | const multiparty = require('multiparty'); 4 | const readChunk = require('read-chunk'); 5 | const fileType = require('file-type'); 6 | const shortid = require('shortid'); 7 | const RouterBase = require('../../../common/routerbase'); 8 | const config = require('../../../config'); 9 | const cos = require('../../../services/cos'); 10 | 11 | class ImageUploader extends RouterBase { 12 | constructor() { 13 | super(...arguments); 14 | 15 | // 图片允许上传的最大文件大小,单位(M) 16 | this.MAX_FILE_SIZE = 5; 17 | } 18 | 19 | handle() { 20 | const result = { 'code': -1, 'msg': '', 'data': {} }; 21 | 22 | this.parseForm() 23 | .then(({ files }) => { 24 | if (!('image' in files)) { 25 | result.msg = '参数错误'; 26 | return; 27 | } 28 | 29 | const imageFile = files.image[0]; 30 | 31 | const buffer = readChunk.sync(imageFile.path, 0, 262); 32 | const resultType = fileType(buffer); 33 | if (!resultType || !['image/jpeg', 'image/png'].includes(resultType.mime)) { 34 | result.msg = '仅jpg/png格式'; 35 | return; 36 | } 37 | 38 | const srcpath = imageFile.path 39 | const imgKey = `${Date.now()}-${shortid.generate()}.${resultType.ext}` 40 | const params = { 41 | Bucket: config.cosFileBucket, 42 | Region: config.cosRegion, 43 | Key: imgKey, 44 | Body: srcpath, 45 | ContentLength: imageFile.size 46 | } 47 | 48 | return new Promise((resolve, reject) => { 49 | cos.putObject(params, (err, data) => { 50 | if (err) reject(err) 51 | 52 | console.log(data) 53 | result.code = 0 54 | result.msg = 'ok' 55 | result.data.imgUrl = config.cosDomain + imgKey 56 | resolve() 57 | 58 | // remove uploaded file 59 | fs.unlink(srcpath) 60 | 61 | }); 62 | }); 63 | 64 | }) 65 | .catch(e => { 66 | console.log(e) 67 | if (e.statusCode === 413) { 68 | result.msg = `单个不超过${this.MAX_FILE_SIZE}MB`; 69 | } else { 70 | result.msg = '图片上传失败,请稍候再试'; 71 | } 72 | }) 73 | .then(() => { 74 | this.res.json(result); 75 | }); 76 | } 77 | 78 | parseForm() { 79 | const form = new multiparty.Form({ 80 | encoding: 'utf8', 81 | maxFilesSize: this.MAX_FILE_SIZE * 1024 * 1024, 82 | autoFiles: true, 83 | uploadDir: path.join(global.SERVER_ROOT, 'tmp'), 84 | }) 85 | 86 | return new Promise((resolve, reject) => { 87 | form.parse(this.req, (err, fields = {}, files = {}) => { 88 | err ? reject(err) : resolve({ fields, files }) 89 | }) 90 | }) 91 | } 92 | } 93 | 94 | module.exports = ImageUploader.makeRouteHandler(); -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 微信小程序示例 - 小相册 2 | 3 | 小相册是结合腾讯云[对象存储服务](https://www.qcloud.com/product/cos.html)(Cloud Object Service,简称COS)制作的一个微信小程序示例。在代码结构上包含如下两部分: 4 | 5 | - `app`: 小相册应用包代码,可直接在微信开发者工具中作为项目打开 6 | - `server`: 搭建的Node服务端代码,作为服务器和`app`通信,提供 CGI 接口示例用于拉取 COS 图片资源、上传图片到 COS、删除 COS 图片 7 | 8 | 小相册主要功能如下: 9 | * 列出 COS 服务器中的图片列表 10 | * 点击左上角**上传图片**图标,可以调用相机拍照或从手机相册选择图片,并将选中的图片上传到 COS 服务器中 11 | * 轻按任意图片,可进入全屏图片预览模式,并可左右滑动切换预览图片 12 | * 长按任意图片,可将其保存到本地,或从 COS 中删除 13 | 14 | ![运行截图](screenshot.png) 15 | 16 | 17 | ## 部署和运行 18 | 19 | 拿到了本小程序源码的朋友可以尝试自己运行起来。 20 | 21 | ### 整体架构 22 | 23 | ![整体架构](http://easyimage-10028115.file.myqcloud.com/internal/t3n2rrlm.ulr.jpg) 24 | 25 | ### 1. 准备域名和证书 26 | 27 | 在微信小程序中,所有的网路请求受到严格限制,不满足条件的域名和协议无法请求,具体包括: 28 | 29 | * 只允许和在 MP 中配置好的域名进行通信,如果还没有域名,需要注册一个。 30 | * 网络请求必须走 HTTPS 协议,所以你还需要为你的域名申请一个 SSL 证书。 31 | 32 | > 腾讯云提供[域名注册](https://www.qcloud.com/product/dm.html)和[证书申请](https://console.qcloud.com/ssl)服务,还没有域名或者证书的可以去使用 33 | 34 | 域名注册好之后,可以登录[微信公众平台](https://mp.weixin.qq.com)配置通信域名了。 35 | 36 | ![配置通信域名](http://easyimage-10028115.file.myqcloud.com/internal/tjzpgjrz.y5a.jpg) 37 | 38 | ### 2. Nginx 和 Node 代码部署 39 | 40 | 小相册服务要运行,需要进行以下几步: 41 | 42 | * 部署 Nginx,Nginx 的安装和部署请大家自行搜索(注意需要把 SSL 模块也编译进去) 43 | * 配置 Nginx 反向代理到 `http://127.0.0.1:9993` 44 | * Node 运行环境,可以安装 [Node V6.6.0](https://nodejs.org/) 45 | * 部署 `server` 目录的代码到服务器上,如 `/data/release/qcloud-applet-album` 46 | * 使用 `npm install` 安装依赖模块 47 | * 使用 `npm install pm2 -g` 安装 pm2 48 | 49 | > 上述环境配置比较麻烦,剪刀石头布的服务器运行代码和配置已经打包成[腾讯云 CVM 镜像](https://buy.qcloud.com/cvm?marketImgId=371),推荐大家直接使用。 50 | > * 镜像部署完成之后,云主机上就有运行 WebSocket 服务的基本环境、代码和配置了。 51 | > * 腾讯云用户可以[免费领取礼包](https://www.qcloud.com/act/event/yingyonghao.html#section-voucher),体验腾讯云小程序解决方案。 52 | > * 镜像已包含「剪刀石头布」和「小相册」两个小程序的服务器环境与代码,需要体验两个小程序的朋友无需重复部署 53 | 54 | ### 3. 配置 HTTPS 55 | 56 | 镜像中已经部署了 nginx,需要在 `/etc/nginx/conf.d` 下修改配置中的域名、证书、私钥。 57 | 58 | ![证书 Nginx 配置](http://easyimage-10028115.file.myqcloud.com/internal/agfty0fn.gfi.jpg) 59 | 60 | 61 | 配置完成后,即可启动 nginx。 62 | 63 | ```sh 64 | nginx 65 | ``` 66 | 67 | ### 4. 域名解析 68 | 69 | 我们还需要添加域名记录解析到我们的云服务器上,这样才可以使用域名进行 HTTPS 服务。 70 | 71 | 在腾讯云注册的域名,可以直接使用[云解析控制台](https://console.qcloud.com/cns/domains)来添加主机记录,直接选择上面购买的 CVM。 72 | 73 | ![添加域名解析](http://easyimage-10028115.file.myqcloud.com/internal/uw25hdj2.k1u.jpg) 74 | 75 | 解析生效后,我们在浏览器使用域名就可以进行 HTTPS 访问。 76 | 77 | ![HTTPS 访问效果图](http://easyimage-10028115.file.myqcloud.com/internal/bxfkmjea.g41.jpg) 78 | 79 | ### 5. 开通和配置 COS 80 | 81 | 小相册示例的图片资源是存储在 COS 上的,要使用 COS 服务,需要登录 [COS 管理控制台](https://console.qcloud.com/cos/overview),然后在其中完成以下操作: 82 | 83 | - 开通 COS 服务分配得到唯一的`APP ID` 84 | - 使用密钥管理生成一对`SecretID`和`SecretKey`(用于调用 COS API) 85 | - 在 Bucket 列表中创建**公有读私有写**访问权限、**CDN加速**的 bucket(存储图片的目标容器) 86 | - 在创建的 bucket 容器中创建文件夹,命名为`photos`,图片将会上传存储到该目录下 87 | 88 | ### 6. 启动小相册示例 Node 服务 89 | 90 | 在镜像中,小相册示例的 Node 服务代码已部署在目录 `/data/release/qcloud-applet-album` 下: 91 | 92 | 进入该目录: 93 | 94 | ```bash 95 | cd /data/release/qcloud-applet-album 96 | ``` 97 | 98 | 在该目录下有个名为`config.js`的配置文件(如下所示),按注释修改对应的 COS 配置: 99 | 100 | ```js 101 | module.exports = { 102 | // Node 监听的端口号 103 | port: '9993', 104 | ROUTE_BASE_PATH: '/applet', 105 | 106 | cosAppId: '填写开通 COS 时分配的 APP ID', 107 | cosSecretId: '填写密钥 SecretID', 108 | cosSecretKey: '填写密钥 SecretKey', 109 | cosFileBucket: '填写创建的公有读私有写的bucket名称', 110 | }; 111 | ``` 112 | 113 | 代码运行需要临时目录,在部署目录下创建一个临时目录 `tmp`。 114 | 115 | ```sh 116 | mkdir tmp 117 | ``` 118 | 119 | 小相册示例使用`pm2`管理 Node 进程,执行以下命令启动 Node 服务: 120 | 121 | ```bash 122 | pm2 start process.json 123 | ``` 124 | 125 | ### 7. 微信小程序服务器配置 126 | 127 | 进入微信公众平台管理后台设置服务器配置,配置类似如下设置: 128 | 129 | ![小程序服务器配置](http://easyimage-10028115.file.myqcloud.com/internal/erz2fmyd.bx1.jpg) 130 | 131 | 注意:需要将 `www.qcloud.la` 设置为上面申请的域名,将 downloadFile 合法域名设置为在 COS 管理控制台中自己创建的 bucket 的相应 **CDN 加速访问地址**,如下图所示: 132 | 133 | ![CDN加速访问地址](http://easyimage-10028115.file.myqcloud.com/internal/1criixcb.wat.jpg) 134 | 135 | ### 8. 启动小相册 Demo 136 | 137 | 在微信开发者工具将小相册应用包源码添加为项目,并把源文件`config.js`中的通讯域名修改成上面申请的域名。 138 | 139 | ![修改配置文件](http://easyimage-10028115.file.myqcloud.com/internal/nvqrghyi.pl4.jpg) 140 | 141 | 然后点击调试即可打开小相册 Demo 开始体验。 142 | 143 | ![调试](http://easyimage-10028115.file.myqcloud.com/internal/uo4m5gcd.tpr.jpg) 144 | 145 | 这里有个问题。截止目前为止,微信小程序提供的上传和下载 API 无法在调试工具中正常工作,需要用手机微信扫码预览体验。 146 | 147 | ## 主要功能实现 148 | 149 | ### 上传图片 150 | 151 | 上传图片使用了微信小程序提供的`wx.chooseImage(OBJECT)`获取需要上传的文件路径,然后调用上传文件接口`wx.request(OBJECT)`发送 HTTPS POST 请求到自己指定的后台服务器。和传统表单文件上传一样,请求头`Content-Type`也是`multipart/form-data`。后台服务器收到请求后,使用 npm 模块 multiparty 解析 `multipart/form-data` 请求,将解析后的数据保存为指定目录下的临时文件。拿到临时文件的路径后,就可直接调用 COS SDK 提供的[文件上传 API](https://www.qcloud.com/doc/product/430/5947#.E6.96.87.E4.BB.B6.E4.B8.8A.E4.BC.A0) 进行图片存储,最后得到图片的存储路径及访问地址(存储的图片路径也可以直接在 COS 管理控制台看到)。 152 | 153 | ### 获取图片列表 154 | 155 | 调用[列举目录下文件&目录 API](https://www.qcloud.com/doc/product/430/5947#.E5.88.97.E4.B8.BE.E7.9B.AE.E5.BD.95.E4.B8.8B.E6.96.87.E4.BB.B6.26amp.3B.E7.9B.AE.E5.BD.95)可以获取到在 COS 服务端指定 bucket 和该 bucket 指定路径下存储的图片。 156 | 157 | ### 下载和保存图片 158 | 159 | 指定图片的访问地址,然后调用微信小程序提供的 `wx.downloadFile(OBJECT)` 和 `wx.saveFile(OBJECT)` 接口可以直接将图片下载和保存本地。这里要注意图片访问地址的域名需要和服务器配置的 downloadFile 合法域名一致,否则无法下载。 160 | 161 | ### 删除图片 162 | 163 | 删除图片也十分简单,直接调用[文件删除 API](https://www.qcloud.com/doc/product/430/5947#.E6.96.87.E4.BB.B6.E5.88.A0.E9.99.A4) 就可以将存储在 COS 服务端的图片删除。 164 | 165 | ## LICENSE 166 | 167 | [MIT](LICENSE) 168 | -------------------------------------------------------------------------------- /app/pages/album/album.js: -------------------------------------------------------------------------------- 1 | const config = require('../../config.js'); 2 | const { listToMatrix, always } = require('../../lib/util.js'); 3 | const request = require('../../lib/request.js'); 4 | const api = require('../../lib/api.js'); 5 | 6 | Page({ 7 | data: { 8 | // 相册列表数据 9 | albumList: [], 10 | 11 | // 图片布局列表(二维数组,由`albumList`计算而得) 12 | layoutList: [], 13 | 14 | // 布局列数 15 | layoutColumnSize: 3, 16 | 17 | // 是否显示loading 18 | showLoading: false, 19 | 20 | // loading提示语 21 | loadingMessage: '', 22 | 23 | // 是否显示toast 24 | showToast: false, 25 | 26 | // 提示消息 27 | toastMessage: '', 28 | 29 | // 是否显示动作命令 30 | showActionsSheet: false, 31 | 32 | // 当前操作的图片 33 | imageInAction: '', 34 | 35 | // 图片预览模式 36 | previewMode: false, 37 | 38 | // 当前预览索引 39 | previewIndex: 0, 40 | }, 41 | 42 | // 显示loading提示 43 | showLoading(loadingMessage) { 44 | this.setData({ showLoading: true, loadingMessage }); 45 | }, 46 | 47 | // 隐藏loading提示 48 | hideLoading() { 49 | this.setData({ showLoading: false, loadingMessage: '' }); 50 | }, 51 | 52 | // 显示toast消息 53 | showToast(toastMessage) { 54 | this.setData({ showToast: true, toastMessage }); 55 | }, 56 | 57 | // 隐藏toast消息 58 | hideToast() { 59 | this.setData({ showToast: false, toastMessage: '' }); 60 | }, 61 | 62 | // 隐藏动作列表 63 | hideActionSheet() { 64 | this.setData({ showActionsSheet: false, imageInAction: '' }); 65 | }, 66 | 67 | onLoad() { 68 | this.renderAlbumList(); 69 | 70 | this.getAlbumList().then((resp) => { 71 | if (resp.code !== 0) { 72 | // 图片列表加载失败 73 | return; 74 | } 75 | 76 | this.setData({ 'albumList': this.data.albumList.concat(resp.data) }); 77 | this.renderAlbumList(); 78 | }); 79 | }, 80 | 81 | // 获取相册列表 82 | getAlbumList() { 83 | this.showLoading('加载列表中…'); 84 | setTimeout(() => this.hideLoading(), 1000); 85 | return request({ method: 'GET', url: api.getUrl('/list') }); 86 | }, 87 | 88 | // 渲染相册列表 89 | renderAlbumList() { 90 | let layoutColumnSize = this.data.layoutColumnSize; 91 | let layoutList = []; 92 | 93 | if (this.data.albumList.length) { 94 | layoutList = listToMatrix([0].concat(this.data.albumList), layoutColumnSize); 95 | 96 | let lastRow = layoutList[layoutList.length - 1]; 97 | if (lastRow.length < layoutColumnSize) { 98 | let supplement = Array(layoutColumnSize - lastRow.length).fill(0); 99 | lastRow.push(...supplement); 100 | } 101 | } 102 | 103 | this.setData({ layoutList }); 104 | }, 105 | 106 | // 从相册选择照片或拍摄照片 107 | chooseImage() { 108 | wx.chooseImage({ 109 | count: 9, 110 | sizeType: ['original', 'compressed'], 111 | sourceType: ['album', 'camera'], 112 | 113 | success: (res) => { 114 | this.showLoading('正在上传图片…'); 115 | 116 | console.log(api.getUrl('/upload')); 117 | wx.uploadFile({ 118 | url: api.getUrl('/upload'), 119 | filePath: res.tempFilePaths[0], 120 | name: 'image', 121 | 122 | success: (res) => { 123 | let response = JSON.parse(res.data); 124 | 125 | if (response.code === 0) { 126 | console.log(response); 127 | 128 | let albumList = this.data.albumList; 129 | albumList.unshift(response.data.imgUrl); 130 | 131 | this.setData({ albumList }); 132 | this.renderAlbumList(); 133 | 134 | this.showToast('图片上传成功'); 135 | } else { 136 | console.log(response); 137 | } 138 | }, 139 | 140 | fail: (res) => { 141 | console.log('fail', res); 142 | }, 143 | 144 | complete: () => { 145 | this.hideLoading(); 146 | }, 147 | }); 148 | 149 | }, 150 | }); 151 | }, 152 | 153 | // 进入预览模式 154 | enterPreviewMode(event) { 155 | if (this.data.showActionsSheet) { 156 | return; 157 | } 158 | 159 | let imageUrl = event.target.dataset.src; 160 | let previewIndex = this.data.albumList.indexOf(imageUrl); 161 | 162 | this.setData({ previewMode: true, previewIndex: previewIndex }); 163 | }, 164 | 165 | // 退出预览模式 166 | leavePreviewMode() { 167 | this.setData({ previewMode: false, previewIndex: 0 }); 168 | }, 169 | 170 | // 显示可操作命令 171 | showActions(event) { 172 | this.setData({ showActionsSheet: true, imageInAction: event.target.dataset.src }); 173 | }, 174 | 175 | // 下载图片 176 | downloadImage() { 177 | this.showLoading('正在保存图片…'); 178 | console.log('download_image_url', this.data.imageInAction); 179 | 180 | wx.downloadFile({ 181 | url: this.data.imageInAction, 182 | type: 'image', 183 | success: (resp) => { 184 | wx.saveFile({ 185 | tempFilePath: resp.tempFilePath, 186 | success: (resp) => { 187 | this.showToast('图片保存成功'); 188 | }, 189 | 190 | fail: (resp) => { 191 | console.log('fail', resp); 192 | }, 193 | 194 | complete: (resp) => { 195 | console.log('complete', resp); 196 | this.hideLoading(); 197 | }, 198 | }); 199 | }, 200 | 201 | fail: (resp) => { 202 | console.log('fail', resp); 203 | }, 204 | }); 205 | 206 | this.setData({ showActionsSheet: false, imageInAction: '' }); 207 | }, 208 | 209 | // 删除图片 210 | deleteImage() { 211 | let imageUrl = this.data.imageInAction; 212 | let filepath = '/' + imageUrl.split('/').slice(3).join('/'); 213 | 214 | this.showLoading('正在删除图片…'); 215 | this.setData({ showActionsSheet: false, imageInAction: '' }); 216 | 217 | request({ 218 | method: 'POST', 219 | url: api.getUrl('/delete'), 220 | data: { filepath }, 221 | }) 222 | .then((resp) => { 223 | if (resp.code !== 0) { 224 | // 图片删除失败 225 | return; 226 | } 227 | 228 | // 从图片列表中移除 229 | let index = this.data.albumList.indexOf(imageUrl); 230 | if (~index) { 231 | let albumList = this.data.albumList; 232 | albumList.splice(index, 1); 233 | 234 | this.setData({ albumList }); 235 | this.renderAlbumList(); 236 | } 237 | 238 | this.showToast('图片删除成功'); 239 | }) 240 | .catch(error => { 241 | console.log('failed', error); 242 | }) 243 | .then(() => { 244 | this.hideLoading(); 245 | }); 246 | }, 247 | }); -------------------------------------------------------------------------------- /app/pages/index/index.wxss: -------------------------------------------------------------------------------- 1 | .page-top { 2 | width: 750rpx; 3 | height: 594rpx; 4 | background-image: url(data:image/png;base64,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); 5 | background-repeat: no-repeat; 6 | background-size: 750rpx 694rpx; 7 | background-position:0 -40rpx; 8 | position: relative; 9 | z-index: 2; 10 | } 11 | .username,.text-info { 12 | position: absolute; 13 | left:50%; 14 | transform: translateX(-50%); 15 | white-space: nowrap; 16 | } 17 | .username { 18 | font-size: 40rpx; 19 | color: #fff; 20 | top:339rpx; 21 | } 22 | .text-info { 23 | font-size: 32rpx; 24 | color:#bdd0ee; 25 | top:400rpx; 26 | } 27 | .page-btn-wrap { 28 | position: absolute; 29 | top: 470rpx; 30 | width: 100%; 31 | text-align: center; 32 | } 33 | .page-btn { 34 | position:relative; 35 | margin:0 20rpx; 36 | padding:0; 37 | box-sizing:border-box; 38 | font-size:32rpx; 39 | text-decoration:none; 40 | -webkit-tap-highlight-color:transparent; 41 | overflow:hidden; 42 | display: inline-block; 43 | width: 300rpx; 44 | height: 85rpx; 45 | background-color: #fff; 46 | color: #2277da; 47 | line-height: 85rpx; 48 | } 49 | .page-bottom { 50 | background-color: #fff; 51 | width: 100%; 52 | height: 100%; 53 | position: absolute; 54 | top: 0; 55 | left: 0; 56 | padding: 624rpx 0 0; 57 | z-index: 1; 58 | box-sizing: border-box; 59 | } 60 | .qr-img { 61 | display: block; 62 | width: 300rpx; 63 | height: 300rpx; 64 | margin: 40rpx auto; 65 | } 66 | .qr-txt { 67 | display: block; 68 | color: #666; 69 | font-size: 32rpx; 70 | margin: 20rpx auto 0; 71 | text-align: center; 72 | } 73 | .page-logo { 74 | display: block; 75 | width: 200rpx; 76 | height: 54rpx; 77 | margin: 40rpx auto 40rpx; 78 | } --------------------------------------------------------------------------------