first commit
17
.gitignore
vendored
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
# Windows
|
||||||
|
[Dd]esktop.ini
|
||||||
|
Thumbs.db
|
||||||
|
$RECYCLE.BIN/
|
||||||
|
|
||||||
|
NodeServer/node_modules/
|
||||||
|
|
||||||
|
# macOS
|
||||||
|
.DS_Store
|
||||||
|
.fseventsd
|
||||||
|
.Spotlight-V100
|
||||||
|
.TemporaryItems
|
||||||
|
.Trashes
|
||||||
|
.idea
|
||||||
|
|
||||||
|
# Node.js
|
||||||
|
node_modules/
|
||||||
201
LICENSE
Normal file
@@ -0,0 +1,201 @@
|
|||||||
|
Apache License
|
||||||
|
Version 2.0, January 2004
|
||||||
|
http://www.apache.org/licenses/
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||||
|
|
||||||
|
1. Definitions.
|
||||||
|
|
||||||
|
"License" shall mean the terms and conditions for use, reproduction,
|
||||||
|
and distribution as defined by Sections 1 through 9 of this document.
|
||||||
|
|
||||||
|
"Licensor" shall mean the copyright owner or entity authorized by
|
||||||
|
the copyright owner that is granting the License.
|
||||||
|
|
||||||
|
"Legal Entity" shall mean the union of the acting entity and all
|
||||||
|
other entities that control, are controlled by, or are under common
|
||||||
|
control with that entity. For the purposes of this definition,
|
||||||
|
"control" means (i) the power, direct or indirect, to cause the
|
||||||
|
direction or management of such entity, whether by contract or
|
||||||
|
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||||
|
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||||
|
|
||||||
|
"You" (or "Your") shall mean an individual or Legal Entity
|
||||||
|
exercising permissions granted by this License.
|
||||||
|
|
||||||
|
"Source" form shall mean the preferred form for making modifications,
|
||||||
|
including but not limited to software source code, documentation
|
||||||
|
source, and configuration files.
|
||||||
|
|
||||||
|
"Object" form shall mean any form resulting from mechanical
|
||||||
|
transformation or translation of a Source form, including but
|
||||||
|
not limited to compiled object code, generated documentation,
|
||||||
|
and conversions to other media types.
|
||||||
|
|
||||||
|
"Work" shall mean the work of authorship, whether in Source or
|
||||||
|
Object form, made available under the License, as indicated by a
|
||||||
|
copyright notice that is included in or attached to the work
|
||||||
|
(an example is provided in the Appendix below).
|
||||||
|
|
||||||
|
"Derivative Works" shall mean any work, whether in Source or Object
|
||||||
|
form, that is based on (or derived from) the Work and for which the
|
||||||
|
editorial revisions, annotations, elaborations, or other modifications
|
||||||
|
represent, as a whole, an original work of authorship. For the purposes
|
||||||
|
of this License, Derivative Works shall not include works that remain
|
||||||
|
separable from, or merely link (or bind by name) to the interfaces of,
|
||||||
|
the Work and Derivative Works thereof.
|
||||||
|
|
||||||
|
"Contribution" shall mean any work of authorship, including
|
||||||
|
the original version of the Work and any modifications or additions
|
||||||
|
to that Work or Derivative Works thereof, that is intentionally
|
||||||
|
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||||
|
or by an individual or Legal Entity authorized to submit on behalf of
|
||||||
|
the copyright owner. For the purposes of this definition, "submitted"
|
||||||
|
means any form of electronic, verbal, or written communication sent
|
||||||
|
to the Licensor or its representatives, including but not limited to
|
||||||
|
communication on electronic mailing lists, source code control systems,
|
||||||
|
and issue tracking systems that are managed by, or on behalf of, the
|
||||||
|
Licensor for the purpose of discussing and improving the Work, but
|
||||||
|
excluding communication that is conspicuously marked or otherwise
|
||||||
|
designated in writing by the copyright owner as "Not a Contribution."
|
||||||
|
|
||||||
|
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||||
|
on behalf of whom a Contribution has been received by Licensor and
|
||||||
|
subsequently incorporated within the Work.
|
||||||
|
|
||||||
|
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
copyright license to reproduce, prepare Derivative Works of,
|
||||||
|
publicly display, publicly perform, sublicense, and distribute the
|
||||||
|
Work and such Derivative Works in Source or Object form.
|
||||||
|
|
||||||
|
3. Grant of Patent License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
(except as stated in this section) patent license to make, have made,
|
||||||
|
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||||
|
where such license applies only to those patent claims licensable
|
||||||
|
by such Contributor that are necessarily infringed by their
|
||||||
|
Contribution(s) alone or by combination of their Contribution(s)
|
||||||
|
with the Work to which such Contribution(s) was submitted. If You
|
||||||
|
institute patent litigation against any entity (including a
|
||||||
|
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||||
|
or a Contribution incorporated within the Work constitutes direct
|
||||||
|
or contributory patent infringement, then any patent licenses
|
||||||
|
granted to You under this License for that Work shall terminate
|
||||||
|
as of the date such litigation is filed.
|
||||||
|
|
||||||
|
4. Redistribution. You may reproduce and distribute copies of the
|
||||||
|
Work or Derivative Works thereof in any medium, with or without
|
||||||
|
modifications, and in Source or Object form, provided that You
|
||||||
|
meet the following conditions:
|
||||||
|
|
||||||
|
(a) You must give any other recipients of the Work or
|
||||||
|
Derivative Works a copy of this License; and
|
||||||
|
|
||||||
|
(b) You must cause any modified files to carry prominent notices
|
||||||
|
stating that You changed the files; and
|
||||||
|
|
||||||
|
(c) You must retain, in the Source form of any Derivative Works
|
||||||
|
that You distribute, all copyright, patent, trademark, and
|
||||||
|
attribution notices from the Source form of the Work,
|
||||||
|
excluding those notices that do not pertain to any part of
|
||||||
|
the Derivative Works; and
|
||||||
|
|
||||||
|
(d) If the Work includes a "NOTICE" text file as part of its
|
||||||
|
distribution, then any Derivative Works that You distribute must
|
||||||
|
include a readable copy of the attribution notices contained
|
||||||
|
within such NOTICE file, excluding those notices that do not
|
||||||
|
pertain to any part of the Derivative Works, in at least one
|
||||||
|
of the following places: within a NOTICE text file distributed
|
||||||
|
as part of the Derivative Works; within the Source form or
|
||||||
|
documentation, if provided along with the Derivative Works; or,
|
||||||
|
within a display generated by the Derivative Works, if and
|
||||||
|
wherever such third-party notices normally appear. The contents
|
||||||
|
of the NOTICE file are for informational purposes only and
|
||||||
|
do not modify the License. You may add Your own attribution
|
||||||
|
notices within Derivative Works that You distribute, alongside
|
||||||
|
or as an addendum to the NOTICE text from the Work, provided
|
||||||
|
that such additional attribution notices cannot be construed
|
||||||
|
as modifying the License.
|
||||||
|
|
||||||
|
You may add Your own copyright statement to Your modifications and
|
||||||
|
may provide additional or different license terms and conditions
|
||||||
|
for use, reproduction, or distribution of Your modifications, or
|
||||||
|
for any such Derivative Works as a whole, provided Your use,
|
||||||
|
reproduction, and distribution of the Work otherwise complies with
|
||||||
|
the conditions stated in this License.
|
||||||
|
|
||||||
|
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||||
|
any Contribution intentionally submitted for inclusion in the Work
|
||||||
|
by You to the Licensor shall be under the terms and conditions of
|
||||||
|
this License, without any additional terms or conditions.
|
||||||
|
Notwithstanding the above, nothing herein shall supersede or modify
|
||||||
|
the terms of any separate license agreement you may have executed
|
||||||
|
with Licensor regarding such Contributions.
|
||||||
|
|
||||||
|
6. Trademarks. This License does not grant permission to use the trade
|
||||||
|
names, trademarks, service marks, or product names of the Licensor,
|
||||||
|
except as required for reasonable and customary use in describing the
|
||||||
|
origin of the Work and reproducing the content of the NOTICE file.
|
||||||
|
|
||||||
|
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||||
|
agreed to in writing, Licensor provides the Work (and each
|
||||||
|
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
implied, including, without limitation, any warranties or conditions
|
||||||
|
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||||
|
appropriateness of using or redistributing the Work and assume any
|
||||||
|
risks associated with Your exercise of permissions under this License.
|
||||||
|
|
||||||
|
8. Limitation of Liability. In no event and under no legal theory,
|
||||||
|
whether in tort (including negligence), contract, or otherwise,
|
||||||
|
unless required by applicable law (such as deliberate and grossly
|
||||||
|
negligent acts) or agreed to in writing, shall any Contributor be
|
||||||
|
liable to You for damages, including any direct, indirect, special,
|
||||||
|
incidental, or consequential damages of any character arising as a
|
||||||
|
result of this License or out of the use or inability to use the
|
||||||
|
Work (including but not limited to damages for loss of goodwill,
|
||||||
|
work stoppage, computer failure or malfunction, or any and all
|
||||||
|
other commercial damages or losses), even if such Contributor
|
||||||
|
has been advised of the possibility of such damages.
|
||||||
|
|
||||||
|
9. Accepting Warranty or Additional Liability. While redistributing
|
||||||
|
the Work or Derivative Works thereof, You may choose to offer,
|
||||||
|
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||||
|
or other liability obligations and/or rights consistent with this
|
||||||
|
License. However, in accepting such obligations, You may act only
|
||||||
|
on Your own behalf and on Your sole responsibility, not on behalf
|
||||||
|
of any other Contributor, and only if You agree to indemnify,
|
||||||
|
defend, and hold each Contributor harmless for any liability
|
||||||
|
incurred by, or claims asserted against, such Contributor by reason
|
||||||
|
of your accepting any such warranty or additional liability.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
APPENDIX: How to apply the Apache License to your work.
|
||||||
|
|
||||||
|
To apply the Apache License to your work, attach the following
|
||||||
|
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||||
|
replaced with your own identifying information. (Don't include
|
||||||
|
the brackets!) The text should be enclosed in the appropriate
|
||||||
|
comment syntax for the file format. We also recommend that a
|
||||||
|
file or class name and description of purpose be included on the
|
||||||
|
same "printed page" as the copyright notice for easier
|
||||||
|
identification within third-party archives.
|
||||||
|
|
||||||
|
Copyright [yyyy] [name of copyright owner]
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
1
NodeServer/.pydio
Normal file
@@ -0,0 +1 @@
|
|||||||
|
51645263-0a19-469f-811e-4b9ab5070e69
|
||||||
1
NodeServer/BroadcastChannel/.pydio
Normal file
@@ -0,0 +1 @@
|
|||||||
|
adc12d86-cd98-4497-aa04-dd7b13c37945
|
||||||
7
NodeServer/BroadcastChannel/index.js
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
const { BroadcastChannel } = require('broadcast-channel');
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
SubscribeExpired: new BroadcastChannel('SubscribeExpired'),
|
||||||
|
SweepStatus: new BroadcastChannel('SweepStatus'),
|
||||||
|
AuthorizedWeb: new BroadcastChannel('AuthorizedWeb')
|
||||||
|
};
|
||||||
33
NodeServer/app.js
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
var createError = require('http-errors');
|
||||||
|
var express = require('express');
|
||||||
|
var path = require('path');
|
||||||
|
var cookieParser = require('cookie-parser');
|
||||||
|
var logger = require('morgan');
|
||||||
|
|
||||||
|
var indexRouter = require('./routes/index');
|
||||||
|
|
||||||
|
var app = express();
|
||||||
|
|
||||||
|
app.use(logger('dev'));
|
||||||
|
app.use(express.json());
|
||||||
|
app.use(express.urlencoded({ extended: false }));
|
||||||
|
app.use(cookieParser());
|
||||||
|
|
||||||
|
app.use('/', indexRouter);
|
||||||
|
|
||||||
|
// catch 404 and forward to error handler
|
||||||
|
app.use(function(req, res, next) {
|
||||||
|
next(createError(404));
|
||||||
|
});
|
||||||
|
|
||||||
|
// error handler
|
||||||
|
app.use(function(err, req, res, next) {
|
||||||
|
// set locals, only providing error in development
|
||||||
|
res.locals.message = err.message;
|
||||||
|
res.locals.error = req.app.get('env') === 'development' ? err : {};
|
||||||
|
|
||||||
|
// render the error page
|
||||||
|
res.status(err.status || 500);
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = app;
|
||||||
1
NodeServer/bin/.pydio
Normal file
@@ -0,0 +1 @@
|
|||||||
|
b6ceb8d6-c456-497e-91f8-db953297899f
|
||||||
87
NodeServer/bin/www
Executable file
@@ -0,0 +1,87 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Module dependencies.
|
||||||
|
*/
|
||||||
|
|
||||||
|
var app = require('../app');
|
||||||
|
var debug = require('debug')(`nodeserver:server`);
|
||||||
|
var http = require('http');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get port from environment and store in Express.
|
||||||
|
*/
|
||||||
|
|
||||||
|
var port = normalizePort(process.env.PORT || '3000');
|
||||||
|
app.set('port', port);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create HTTP server.
|
||||||
|
*/
|
||||||
|
|
||||||
|
var server = http.createServer(app);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Listen on provided port, on all network interfaces.
|
||||||
|
*/
|
||||||
|
|
||||||
|
server.listen(port);
|
||||||
|
server.on('error', onError);
|
||||||
|
server.on('listening', onListening);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Normalize a port into a number, string, or false.
|
||||||
|
*/
|
||||||
|
|
||||||
|
function normalizePort(val) {
|
||||||
|
var port = parseInt(val, 10);
|
||||||
|
|
||||||
|
if (isNaN(port)) {
|
||||||
|
// named pipe
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (port >= 0) {
|
||||||
|
// port number
|
||||||
|
return port;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Event listener for HTTP server "error" event.
|
||||||
|
*/
|
||||||
|
|
||||||
|
function onError(error) {
|
||||||
|
if (error.syscall !== 'listen') {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
|
||||||
|
var bind = typeof port === 'string'
|
||||||
|
? 'Pipe ' + port
|
||||||
|
: 'Port ' + port;
|
||||||
|
|
||||||
|
// handle specific listen errors with friendly messages
|
||||||
|
switch (error.code) {
|
||||||
|
case 'EACCES':
|
||||||
|
console.error(bind + ' requires elevated privileges');
|
||||||
|
process.exit(1);
|
||||||
|
break;
|
||||||
|
case 'EADDRINUSE':
|
||||||
|
console.error(bind + ' is already in use');
|
||||||
|
process.exit(1);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Event listener for HTTP server "listening" event.
|
||||||
|
*/
|
||||||
|
|
||||||
|
function onListening() {
|
||||||
|
var addr = server.address();
|
||||||
|
console.log(`App Is Run: http://127.0.0.1:${addr.port}`)
|
||||||
|
}
|
||||||
1
NodeServer/mysql/.pydio
Normal file
@@ -0,0 +1 @@
|
|||||||
|
254af4fe-8f9b-4edd-b6cf-893f0cc8185a
|
||||||
21
NodeServer/mysql/index.js
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
const mysql = require("yn-mysql-utils");
|
||||||
|
|
||||||
|
class MSql {
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.db = new mysql({
|
||||||
|
host : '127.0.0.1',
|
||||||
|
user : 'root',
|
||||||
|
password : 'lb714500',
|
||||||
|
database : 'ScanCodeLogin',
|
||||||
|
connectionLimit : 10
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async query (params) {
|
||||||
|
return await this.db.Query({ sql: params.sql, par: params.data })
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = new MSql();
|
||||||
22
NodeServer/package.json
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
{
|
||||||
|
"name": "nodeserver",
|
||||||
|
"version": "0.0.0",
|
||||||
|
"private": true,
|
||||||
|
"scripts": {
|
||||||
|
"dev": "node-dev ./bin/www",
|
||||||
|
"socket": "node-dev ./routes/socket.js"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"broadcast-channel": "^3.1.0",
|
||||||
|
"cookie-parser": "~1.4.4",
|
||||||
|
"debug": "~2.6.9",
|
||||||
|
"express": "~4.16.1",
|
||||||
|
"http-errors": "~1.6.3",
|
||||||
|
"morgan": "~1.9.1",
|
||||||
|
"qr-image": "^3.2.0",
|
||||||
|
"redis": "^3.0.2",
|
||||||
|
"socket.io": "^2.3.0",
|
||||||
|
"string-random": "^0.1.3",
|
||||||
|
"yn-mysql-utils": "^1.0.4"
|
||||||
|
}
|
||||||
|
}
|
||||||
1
NodeServer/redis/.pydio
Normal file
@@ -0,0 +1 @@
|
|||||||
|
6fc9f101-e55b-4932-8cfc-af46363fd9da
|
||||||
45
NodeServer/redis/index.js
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
const redis = require("redis");
|
||||||
|
const BroadcastChannel = require("../BroadcastChannel");
|
||||||
|
|
||||||
|
class Redis {
|
||||||
|
constructor() {
|
||||||
|
this.client = redis.createClient({
|
||||||
|
host:'127.0.0.1',
|
||||||
|
port:'6379',
|
||||||
|
ttl: 5*60*1000
|
||||||
|
});
|
||||||
|
|
||||||
|
this.client.on("error", function(error) {
|
||||||
|
console.error(error);
|
||||||
|
});
|
||||||
|
|
||||||
|
this.client.send_command('config', ['set', 'notify-keyspace-events', 'Ex'], this.SubscribeExpired);
|
||||||
|
}
|
||||||
|
|
||||||
|
async get(key) {
|
||||||
|
return new Promise((resolve)=>{
|
||||||
|
this.client.get(key, (err,res) => resolve(res));
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
set(key, value="") {
|
||||||
|
this.client.set(key, value);
|
||||||
|
// 设置key的过期时间60秒(其实也就是二维码的有效时间)
|
||||||
|
this.client.expire(key, 60)
|
||||||
|
}
|
||||||
|
|
||||||
|
SubscribeExpired(e, r) {
|
||||||
|
let sub = redis.createClient(6379);
|
||||||
|
const expired_subKey = '__keyevent@0__:expired';
|
||||||
|
sub.subscribe(expired_subKey, function () {
|
||||||
|
sub.on('message', function (chan, msg) {
|
||||||
|
console.log(`二维码:${msg} 已超时过期... `);
|
||||||
|
// 二维码失效,通过 BroadcastChannel 通知 routes/socket.js
|
||||||
|
BroadcastChannel.SubscribeExpired.postMessage(msg);
|
||||||
|
});
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = new Redis();
|
||||||
|
|
||||||
1
NodeServer/routes/.pydio
Normal file
@@ -0,0 +1 @@
|
|||||||
|
3fa0f904-8283-42e5-bf5b-a1b1c84cadc7
|
||||||
129
NodeServer/routes/index.js
Normal file
@@ -0,0 +1,129 @@
|
|||||||
|
const express = require('express');
|
||||||
|
const router = express.Router();
|
||||||
|
const sql = require("../mysql");
|
||||||
|
const redis = require("../redis");
|
||||||
|
const BroadcastChannel = require("../BroadcastChannel");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户已扫码但是未确认,这个接口只是为了通知web端,用户当前的进展状态
|
||||||
|
*/
|
||||||
|
router.get('/SweepStatus', async (req, res, next) => {
|
||||||
|
let uuid = req.query.uuid;
|
||||||
|
BroadcastChannel.SweepStatus.postMessage(uuid);
|
||||||
|
res.json({ status: 200, msg: '发送成功', data: null })
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 移动端App确认登录按钮点击时候的ajax处理
|
||||||
|
*/
|
||||||
|
router.get('/login', async (req, res, next) => {
|
||||||
|
let uuid = req.query.uuid; // uuid
|
||||||
|
let uid = req.query.uid; // 用户id
|
||||||
|
|
||||||
|
let RedisUid = await redis.get(uuid);
|
||||||
|
if (RedisUid == null || RedisUid == undefined) {
|
||||||
|
res.json({
|
||||||
|
status: 404,
|
||||||
|
msg: '二维码失效或不存在',
|
||||||
|
data: null
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
if (RedisUid.length !== 0) {
|
||||||
|
res.json({
|
||||||
|
status: 404,
|
||||||
|
msg: '已登录,请勿重复扫码',
|
||||||
|
data: null
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// 查询用户信息
|
||||||
|
let user = (await sql.query({ sql: 'select * from user where id = ?', data: [ uid ] }))[0];
|
||||||
|
// 保存用户数据到对应key的redis中
|
||||||
|
redis.set(uuid, JSON.stringify(user));
|
||||||
|
res.json({
|
||||||
|
status: 200,
|
||||||
|
msg: '扫码成功',
|
||||||
|
data: null
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODO
|
||||||
|
* 被移动端App轮询的接口,主要让移动端知道,我移动端现在已登录的用户所对
|
||||||
|
* 应的web端是否在请求让我授权登录,如果在web端在请求登录,那么移动端自
|
||||||
|
* 动进入授权页面让用户授权,授权后结束轮询,否则一直轮询
|
||||||
|
* PS: uniapp 无法连接到 Node.js 的 socket.io,所以只能用很low而且
|
||||||
|
* 很消耗移动端资源的轮询了。等解决这个问题自然用socket重写这里实现
|
||||||
|
*/
|
||||||
|
router.get('/InquiryWebIsLogining', async (req, res, next) => {
|
||||||
|
let uid = req.query.uid; // 用户id
|
||||||
|
// console.log("uid: ", uid);
|
||||||
|
let UserId = await redis.get(uid);
|
||||||
|
// 没有查询到redis的 uid 数据的话
|
||||||
|
if (UserId == null || UserId == undefined) {
|
||||||
|
res.json({
|
||||||
|
status: 404,
|
||||||
|
msg: '没有对应的web端请求登录',
|
||||||
|
data: null
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// UserId 有数值,但是为空,表示web请求登录,但移动端未授权
|
||||||
|
if (UserId.length == 0) {
|
||||||
|
res.json({
|
||||||
|
status: 200,
|
||||||
|
msg: 'web端正在请求授权',
|
||||||
|
data: null
|
||||||
|
});
|
||||||
|
// UserId 有数值,但是不为空,表示web请求登录,移动端已授权,并且redis未超时
|
||||||
|
} else {
|
||||||
|
res.json({
|
||||||
|
status: 500,
|
||||||
|
msg: '授权成功,请勿重复授权',
|
||||||
|
data: null
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 授权web端登录的接口,主要被移动端App调用拿到移动端给的用户id,
|
||||||
|
* 然后从数据库获取用户的详细数据,通过广播把用户数据通知给web端
|
||||||
|
*/
|
||||||
|
router.get('/AuthorizedWeb', async (req, res, next) => {
|
||||||
|
let uid = req.query.uid; // 用户id
|
||||||
|
let UserId = await redis.get(uid);
|
||||||
|
|
||||||
|
if (UserId == null || UserId == undefined) {
|
||||||
|
res.json({
|
||||||
|
status: 404,
|
||||||
|
msg: '没有对应的web端请求登录',
|
||||||
|
data: null
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// UserId 有数值,但是为空
|
||||||
|
if (UserId.length == 0) {
|
||||||
|
// 查询用户信息
|
||||||
|
let user = (await sql.query({ sql: 'select * from user where id = ?', data: [ uid ] }))[0];
|
||||||
|
// 保存用户数据到对应key的redis中
|
||||||
|
redis.set(uid, JSON.stringify(user));
|
||||||
|
BroadcastChannel.AuthorizedWeb.postMessage(user);
|
||||||
|
res.json({
|
||||||
|
status: 200,
|
||||||
|
msg: '成功授权',
|
||||||
|
data: null
|
||||||
|
});
|
||||||
|
|
||||||
|
// UserId 有数值,但是不为空,表示web请求登录,移动端已授权,并且redis未超时
|
||||||
|
} else {
|
||||||
|
res.json({
|
||||||
|
status: 404,
|
||||||
|
msg: '授权成功,请勿重复授权',
|
||||||
|
data: null
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
module.exports = router;
|
||||||
68
NodeServer/routes/socket.js
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
const app = require('express')();
|
||||||
|
const http = require('http').createServer(app);
|
||||||
|
const io = require('socket.io')(http);
|
||||||
|
const utils = require('../utils');
|
||||||
|
const redis = require('../redis');
|
||||||
|
const BroadcastChannel = require("../BroadcastChannel");
|
||||||
|
|
||||||
|
io.on('connection', (socket) => {
|
||||||
|
|
||||||
|
// Web端获取二维码和uuid
|
||||||
|
socket.on('getQRcode', () => {
|
||||||
|
let uuid = utils.randomNumber(); // 生成uuid
|
||||||
|
let QRcode = utils.getQRcode(uuid); // 根据uuid生成二维码的base64
|
||||||
|
redis.set(uuid, ''); // 保存uuid 到redis中,但是用户数据留空,由移动端上传
|
||||||
|
socket.emit('sendQRcode', { uuid: uuid, QRcode: QRcode }); // 向web端发送uuid和二维码base64数据
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查移动端用户是否扫码二维码登录,如果扫码登录了
|
||||||
|
* 那么二维码显示登录成功样式,并显示登录用户信息
|
||||||
|
*/
|
||||||
|
socket.on('checkScanCode', async key => {
|
||||||
|
console.log(`Web端(Vue)在询问用户是否扫码:${key}...`);
|
||||||
|
|
||||||
|
let userInfo = await redis.get(key);
|
||||||
|
// 如果key对应的value为空,那么用户未扫码
|
||||||
|
// 反之 用户扫码并确定登录
|
||||||
|
userInfo == null || userInfo.length == 0 ? socket.emit("waitScanCode") : socket.emit("SuccessScanCode", userInfo);
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 场景:用户已经移动端App扫码登录后,本地保存有用户的id
|
||||||
|
* 功能:模仿微信web端发送请求,然后移动端授权web端的本次登录
|
||||||
|
* web端请求移动端授权登录
|
||||||
|
*/
|
||||||
|
socket.on('WebRequestLogin', async id => {
|
||||||
|
console.log("web端请求登录用户的id: ", id);
|
||||||
|
let WebId = await redis.get(id);
|
||||||
|
|
||||||
|
// 如果redis中没有数值,表示web端还未开始请求移动端授权登录,那么保存id为redis key
|
||||||
|
// 如果redis中有数值,表示web端用户已经在请求移动端授权登录,那么不重复保存id,并且提示web端
|
||||||
|
if (WebId == null) {
|
||||||
|
redis.set(id, ''); // 使用web端的用户id作为redis的key存储起来,value对应的数值由移动端填充
|
||||||
|
} else {
|
||||||
|
console.log("不等于null")
|
||||||
|
socket.emit("DoNotRepeatRequest", '已发送登录请求,请在移动端授权本次登录')
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 接受redis key超时的广播通知,也就是用户长时间未扫二维码主要在redis/index.js中进行订阅key失效的事件
|
||||||
|
// 然后广播通知被这里代码onmessage到,然后 socket 通知 Vue 前端
|
||||||
|
BroadcastChannel.SubscribeExpired.onmessage = event => socket.emit("ScanCodeOvertime");
|
||||||
|
|
||||||
|
// 用户已扫码,但是未确认
|
||||||
|
BroadcastChannel.SweepStatus.onmessage = event => socket.emit("ScanCodeing", event.data);
|
||||||
|
|
||||||
|
// 移动端成功授权web端
|
||||||
|
BroadcastChannel.AuthorizedWeb.onmessage = function(event) {
|
||||||
|
console.log("event: ", event);
|
||||||
|
socket.emit("AuthorizedWebSuccess", event);
|
||||||
|
};
|
||||||
|
|
||||||
|
socket.on('disconnect', () => console.log('web端用户掉线...'));
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
http.listen(9080, () => console.log('socket listening on *: 9080'));
|
||||||
1
NodeServer/utils/.pydio
Normal file
@@ -0,0 +1 @@
|
|||||||
|
c5a72c45-7bb0-4bec-8631-53fb9b81e78f
|
||||||
17
NodeServer/utils/index.js
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
var random = require('string-random');
|
||||||
|
var qr = require('qr-image');
|
||||||
|
|
||||||
|
class Utils {
|
||||||
|
|
||||||
|
// 产随机字符串
|
||||||
|
randomNumber() {
|
||||||
|
return 'login/' + random(16, { numbers: true })
|
||||||
|
}
|
||||||
|
|
||||||
|
// 根据 randomNumber 的随机随机字符串生成base64二维码
|
||||||
|
getQRcode(str) {
|
||||||
|
return `data:image/png;base64,${qr.imageSync(str,{ type:'png' }).toString("base64")}`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = new Utils();
|
||||||
116
README.md
Normal file
@@ -0,0 +1,116 @@
|
|||||||
|
## 1:仿微信,QQ,微博扫码登录
|
||||||
|
|
||||||
|
一直都感觉扫码登录很神奇啊!!然后谷歌一搜思路一看,发现还真是挺简单的,所以技痒自己写个玩玩,代码里面的实现方式和思路可以借鉴。不要问为什么不用`PHP`,`Java`,我不会告诉你`Node.js`写起来更简单更方便,至于用什么后端语言实现这个不是关键,看你个人爱好。
|
||||||
|
|
||||||
|
**哦,对了。这里实现的是扫码登录的一整套代码,看懂了拿过去改改封装一下就可以在项目中使用了。这其中包含服务端(NodeServer),App端(UniAppMobile),Web端(VueQRCode),可以直接部署跑起来哦。
|
||||||
|
不过你要先准备好以下环境并且掌握不少的前后端技术点才能愉快玩耍哦:**
|
||||||
|
|
||||||
|
- Nodejs
|
||||||
|
- Mysql
|
||||||
|
- Redis
|
||||||
|
- Uni-app
|
||||||
|
- Socket
|
||||||
|
- Express
|
||||||
|
- Vue.js
|
||||||
|
|
||||||
|
来自大佬 [尤雨溪](https://baike.baidu.com/item/%E5%B0%A4%E9%9B%A8%E6%BA%AA/2281470?fr=aladdin) 的鼓励:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
无图无真相,下面请接招:
|
||||||
|
|
||||||
|
**效果图:**
|
||||||
|
|
||||||
|
1.1 Web端未扫码:
|
||||||
|

|
||||||
|
|
||||||
|
1.2 App端已扫码,但未在App端确认登录:
|
||||||
|

|
||||||
|
|
||||||
|
1.3 Web端二维码失效:
|
||||||
|

|
||||||
|
|
||||||
|
1.4 Web端扫码成功:
|
||||||
|

|
||||||
|
|
||||||
|
1.5 App端首页
|
||||||
|

|
||||||
|
|
||||||
|
1.6 App端扫码页面
|
||||||
|

|
||||||
|
|
||||||
|
1.7 App端确认登录
|
||||||
|

|
||||||
|
|
||||||
|
|
||||||
|
## 2:实现的功能
|
||||||
|
|
||||||
|
额,类似你用过的`QQ`,`微信`,`微博`扫码登录的那些功能,简单说下:
|
||||||
|
|
||||||
|
- [x] web端从服务器获取二维码和uuid
|
||||||
|
- [x] web端处理二维码失效,重复扫码等
|
||||||
|
- [x] redis控制key的超时时间,前端二维码超时样式控制
|
||||||
|
- [x] App端扫码成功后,web端的样式控制和用户信息显示
|
||||||
|
- [x] App端首页扫码后进入login页面确认登录
|
||||||
|
- [x] 二维码失效后,可以点击重新获取
|
||||||
|
- [x] App端首页扫码后通知 --> 服务器--> web端 展示最新扫码进度
|
||||||
|
- [x] 已通过扫码登录后,可以使用【移动端授权登录】来唤醒App对web端登录进行授权
|
||||||
|
|
||||||
|
## 3 实现思路
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## 4:项目结构
|
||||||
|
|
||||||
|
下面先看下项目的基本结构,后面再说实现思路。
|
||||||
|
|
||||||
|
### 4.1 NodeServer - 服务端
|
||||||
|
|
||||||
|
主要用`Node.js`实现后端,通过`socket.io`+`redis`实现`web`端的二维码生成,`uuid`等,同时还实现了`android`端的扫码登录的接口等。具体看下面目录结构
|
||||||
|
|
||||||
|
```text
|
||||||
|
├── BroadcastChannel # 广播频道,主要为了让在socket.io中能及时知道 redis 订阅key的失效事件
|
||||||
|
│ └── index.js
|
||||||
|
├── app.js # express 框架的启动入口文件
|
||||||
|
├── bin # express 框架的启动脚本文件
|
||||||
|
│ └── www
|
||||||
|
├── mysql # mysql的封装,主要使用了我自己开发的npm插件"yn-mysql-utils"
|
||||||
|
│ └── index.js
|
||||||
|
├── node_modules # 依赖包,没什么好说的
|
||||||
|
├── package.json # 项目依赖配置文件,没什么好说的
|
||||||
|
├── redis # redis 的简单封装
|
||||||
|
│ └── index.js
|
||||||
|
├── routes # 路由接口
|
||||||
|
│ ├── index.js # 这里面实现的接口主要给APP端使用
|
||||||
|
│ └── socket.js # 实现socket.io,主要给Vue的web端使用
|
||||||
|
└── utils # 工具类的封装
|
||||||
|
├── index.js
|
||||||
|
```
|
||||||
|
### 4.2 UniAppMobile - Android App端
|
||||||
|
|
||||||
|
使用`uni-app`开发的`android`端,主要为了实现扫码然后把`App`上已登录的用户信息GET传到`redis`中。没什么,很简单。主要逻辑在:
|
||||||
|
|
||||||
|
- pages/index/index.vue
|
||||||
|
- pages/login/login.vue
|
||||||
|
|
||||||
|
如果`uni-app`不会,请自行看官方文档学习:
|
||||||
|
|
||||||
|
> [uni-app](https://uniapp.dcloud.io/)
|
||||||
|
|
||||||
|
### 4.3 VueQRCode - Vue web端
|
||||||
|
|
||||||
|
主要使用`Vue`写的`Web`,代码里面注释很多,看看就明白了。主要的代码逻辑都在这个`web`端中。
|
||||||
|
|
||||||
|
## 5 关于我
|
||||||
|
|
||||||
|
一个热爱编程喜欢折腾的人,一个曾经的后端但如今迷失在前端技术中的人,一个喜欢各种技术的人,更多开源项目请看:
|
||||||
|
|
||||||
|
- [HtmlToPug](https://gitee.com/bmycode/HtmlToPug)
|
||||||
|
|
||||||
|
- [Whtml](https://www.npmjs.com/package/whtml)
|
||||||
|
|
||||||
|
- [Vue3Template](https://github.com/helpcode/Vue3Template)
|
||||||
|
|
||||||
|
- [vue3decorators](https://www.npmjs.com/package/vue3decorators)
|
||||||
|
|
||||||
|
- [vue-cli-plugin-autorouter](https://www.npmjs.com/package/vue-cli-plugin-autorouter)
|
||||||
1
Resources/.pydio
Normal file
@@ -0,0 +1 @@
|
|||||||
|
70d76944-3cda-416c-9483-a49d892a61ae
|
||||||
BIN
Resources/app-index.png
Normal file
|
After Width: | Height: | Size: 65 KiB |
BIN
Resources/app-login.png
Normal file
|
After Width: | Height: | Size: 51 KiB |
BIN
Resources/app-scan.jpg
Normal file
|
After Width: | Height: | Size: 73 KiB |
BIN
Resources/haha.jpeg
Normal file
|
After Width: | Height: | Size: 66 KiB |
BIN
Resources/silu.jpg
Normal file
|
After Width: | Height: | Size: 181 KiB |
BIN
Resources/web-end.png
Normal file
|
After Width: | Height: | Size: 94 KiB |
BIN
Resources/web-ing.png
Normal file
|
After Width: | Height: | Size: 90 KiB |
BIN
Resources/web-start.png
Normal file
|
After Width: | Height: | Size: 91 KiB |
BIN
Resources/web-success.png
Normal file
|
After Width: | Height: | Size: 92 KiB |
1
UniAppMobile/.pydio
Normal file
@@ -0,0 +1 @@
|
|||||||
|
a8d049cb-d9e2-4fd3-9416-1241476da7ec
|
||||||
19
UniAppMobile/App.vue
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
onLaunch: function() {
|
||||||
|
// 模拟数据,保存用户id,实际项目中这应该是用户token
|
||||||
|
uni.setStorageSync('userinfo', { id: 1 });
|
||||||
|
console.log('App Launch')
|
||||||
|
},
|
||||||
|
onShow: function() {
|
||||||
|
|
||||||
|
},
|
||||||
|
onHide: function() {
|
||||||
|
console.log('App Hide')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
/*每个页面公共css */
|
||||||
|
</style>
|
||||||
1
UniAppMobile/config/.pydio
Normal file
@@ -0,0 +1 @@
|
|||||||
|
acfc842c-161f-4645-9466-9c72847944a6
|
||||||
3
UniAppMobile/config/index.js
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
export default {
|
||||||
|
baseUrl: 'http://192.168.0.133:3000'
|
||||||
|
}
|
||||||
13
UniAppMobile/main.js
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
import Vue from 'vue'
|
||||||
|
import App from './App'
|
||||||
|
import config from './config/index.js'
|
||||||
|
|
||||||
|
Vue.config.productionTip = false
|
||||||
|
|
||||||
|
App.mpType = 'app'
|
||||||
|
Vue.prototype.$config = config
|
||||||
|
|
||||||
|
const app = new Vue({
|
||||||
|
...App
|
||||||
|
})
|
||||||
|
app.$mount()
|
||||||
75
UniAppMobile/manifest.json
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
{
|
||||||
|
"name" : "rr",
|
||||||
|
"appid" : "__UNI__D277C34",
|
||||||
|
"description" : "",
|
||||||
|
"versionName" : "1.0.0",
|
||||||
|
"versionCode" : "100",
|
||||||
|
"transformPx" : false,
|
||||||
|
/* 5+App特有相关 */
|
||||||
|
"app-plus" : {
|
||||||
|
"usingComponents" : true,
|
||||||
|
"nvueCompiler" : "uni-app",
|
||||||
|
"compilerVersion" : 3,
|
||||||
|
"splashscreen" : {
|
||||||
|
"alwaysShowBeforeRender" : true,
|
||||||
|
"waiting" : true,
|
||||||
|
"autoclose" : true,
|
||||||
|
"delay" : 0
|
||||||
|
},
|
||||||
|
/* 模块配置 */
|
||||||
|
"modules" : {},
|
||||||
|
/* 应用发布信息 */
|
||||||
|
"distribute" : {
|
||||||
|
/* android打包配置 */
|
||||||
|
"android" : {
|
||||||
|
"permissions" : [
|
||||||
|
"<uses-permission android:name=\"android.permission.CHANGE_NETWORK_STATE\"/>",
|
||||||
|
"<uses-permission android:name=\"android.permission.MOUNT_UNMOUNT_FILESYSTEMS\"/>",
|
||||||
|
"<uses-permission android:name=\"android.permission.READ_CONTACTS\"/>",
|
||||||
|
"<uses-permission android:name=\"android.permission.VIBRATE\"/>",
|
||||||
|
"<uses-permission android:name=\"android.permission.READ_LOGS\"/>",
|
||||||
|
"<uses-permission android:name=\"android.permission.ACCESS_WIFI_STATE\"/>",
|
||||||
|
"<uses-feature android:name=\"android.hardware.camera.autofocus\"/>",
|
||||||
|
"<uses-permission android:name=\"android.permission.WRITE_CONTACTS\"/>",
|
||||||
|
"<uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\"/>",
|
||||||
|
"<uses-permission android:name=\"android.permission.CAMERA\"/>",
|
||||||
|
"<uses-permission android:name=\"android.permission.RECORD_AUDIO\"/>",
|
||||||
|
"<uses-permission android:name=\"android.permission.GET_ACCOUNTS\"/>",
|
||||||
|
"<uses-permission android:name=\"android.permission.MODIFY_AUDIO_SETTINGS\"/>",
|
||||||
|
"<uses-permission android:name=\"android.permission.READ_PHONE_STATE\"/>",
|
||||||
|
"<uses-permission android:name=\"android.permission.CHANGE_WIFI_STATE\"/>",
|
||||||
|
"<uses-permission android:name=\"android.permission.WAKE_LOCK\"/>",
|
||||||
|
"<uses-permission android:name=\"android.permission.CALL_PHONE\"/>",
|
||||||
|
"<uses-permission android:name=\"android.permission.FLASHLIGHT\"/>",
|
||||||
|
"<uses-permission android:name=\"android.permission.ACCESS_COARSE_LOCATION\"/>",
|
||||||
|
"<uses-feature android:name=\"android.hardware.camera\"/>",
|
||||||
|
"<uses-permission android:name=\"android.permission.ACCESS_FINE_LOCATION\"/>",
|
||||||
|
"<uses-permission android:name=\"android.permission.WRITE_SETTINGS\"/>"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
/* ios打包配置 */
|
||||||
|
"ios" : {},
|
||||||
|
/* SDK配置 */
|
||||||
|
"sdkConfigs" : {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
/* 快应用特有相关 */
|
||||||
|
"quickapp" : {},
|
||||||
|
/* 小程序特有相关 */
|
||||||
|
"mp-weixin" : {
|
||||||
|
"appid" : "",
|
||||||
|
"setting" : {
|
||||||
|
"urlCheck" : false
|
||||||
|
},
|
||||||
|
"usingComponents" : true
|
||||||
|
},
|
||||||
|
"mp-alipay" : {
|
||||||
|
"usingComponents" : true
|
||||||
|
},
|
||||||
|
"mp-baidu" : {
|
||||||
|
"usingComponents" : true
|
||||||
|
},
|
||||||
|
"mp-toutiao" : {
|
||||||
|
"usingComponents" : true
|
||||||
|
}
|
||||||
|
}
|
||||||
294
UniAppMobile/package-lock.json
generated
Normal file
@@ -0,0 +1,294 @@
|
|||||||
|
{
|
||||||
|
"requires": true,
|
||||||
|
"lockfileVersion": 1,
|
||||||
|
"dependencies": {
|
||||||
|
"after": {
|
||||||
|
"version": "0.8.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/after/-/after-0.8.2.tgz",
|
||||||
|
"integrity": "sha1-/ts5T58OAqqXaOcCvaI7UF+ufh8="
|
||||||
|
},
|
||||||
|
"arraybuffer.slice": {
|
||||||
|
"version": "0.0.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/arraybuffer.slice/-/arraybuffer.slice-0.0.7.tgz",
|
||||||
|
"integrity": "sha512-wGUIVQXuehL5TCqQun8OW81jGzAWycqzFF8lFp+GOM5BXLYj3bKNsYC4daB7n6XjCqxQA/qgTJ+8ANR3acjrog=="
|
||||||
|
},
|
||||||
|
"async-limiter": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz",
|
||||||
|
"integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ=="
|
||||||
|
},
|
||||||
|
"backo2": {
|
||||||
|
"version": "1.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/backo2/-/backo2-1.0.2.tgz",
|
||||||
|
"integrity": "sha1-MasayLEpNjRj41s+u2n038+6eUc="
|
||||||
|
},
|
||||||
|
"base64-arraybuffer": {
|
||||||
|
"version": "0.1.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-0.1.5.tgz",
|
||||||
|
"integrity": "sha1-c5JncZI7Whl0etZmqlzUv5xunOg="
|
||||||
|
},
|
||||||
|
"better-assert": {
|
||||||
|
"version": "1.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/better-assert/-/better-assert-1.0.2.tgz",
|
||||||
|
"integrity": "sha1-QIZrnhueC1W0gYlDEeaPr/rrxSI=",
|
||||||
|
"requires": {
|
||||||
|
"callsite": "1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"blob": {
|
||||||
|
"version": "0.0.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/blob/-/blob-0.0.5.tgz",
|
||||||
|
"integrity": "sha512-gaqbzQPqOoamawKg0LGVd7SzLgXS+JH61oWprSLH+P+abTczqJbhTR8CmJ2u9/bUYNmHTGJx/UEmn6doAvvuig=="
|
||||||
|
},
|
||||||
|
"callsite": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/callsite/-/callsite-1.0.0.tgz",
|
||||||
|
"integrity": "sha1-KAOY5dZkvXQDi28JBRU+borxvCA="
|
||||||
|
},
|
||||||
|
"component-bind": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/component-bind/-/component-bind-1.0.0.tgz",
|
||||||
|
"integrity": "sha1-AMYIq33Nk4l8AAllGx06jh5zu9E="
|
||||||
|
},
|
||||||
|
"component-emitter": {
|
||||||
|
"version": "1.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz",
|
||||||
|
"integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY="
|
||||||
|
},
|
||||||
|
"component-inherit": {
|
||||||
|
"version": "0.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/component-inherit/-/component-inherit-0.0.3.tgz",
|
||||||
|
"integrity": "sha1-ZF/ErfWLcrZJ1crmUTVhnbJv8UM="
|
||||||
|
},
|
||||||
|
"debug": {
|
||||||
|
"version": "4.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
|
||||||
|
"integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
|
||||||
|
"requires": {
|
||||||
|
"ms": "^2.1.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"engine.io-client": {
|
||||||
|
"version": "3.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-3.4.0.tgz",
|
||||||
|
"integrity": "sha512-a4J5QO2k99CM2a0b12IznnyQndoEvtA4UAldhGzKqnHf42I3Qs2W5SPnDvatZRcMaNZs4IevVicBPayxYt6FwA==",
|
||||||
|
"requires": {
|
||||||
|
"component-emitter": "1.2.1",
|
||||||
|
"component-inherit": "0.0.3",
|
||||||
|
"debug": "~4.1.0",
|
||||||
|
"engine.io-parser": "~2.2.0",
|
||||||
|
"has-cors": "1.1.0",
|
||||||
|
"indexof": "0.0.1",
|
||||||
|
"parseqs": "0.0.5",
|
||||||
|
"parseuri": "0.0.5",
|
||||||
|
"ws": "~6.1.0",
|
||||||
|
"xmlhttprequest-ssl": "~1.5.4",
|
||||||
|
"yeast": "0.1.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"engine.io-parser": {
|
||||||
|
"version": "2.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-2.2.0.tgz",
|
||||||
|
"integrity": "sha512-6I3qD9iUxotsC5HEMuuGsKA0cXerGz+4uGcXQEkfBidgKf0amsjrrtwcbwK/nzpZBxclXlV7gGl9dgWvu4LF6w==",
|
||||||
|
"requires": {
|
||||||
|
"after": "0.8.2",
|
||||||
|
"arraybuffer.slice": "~0.0.7",
|
||||||
|
"base64-arraybuffer": "0.1.5",
|
||||||
|
"blob": "0.0.5",
|
||||||
|
"has-binary2": "~1.0.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"has-binary2": {
|
||||||
|
"version": "1.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/has-binary2/-/has-binary2-1.0.3.tgz",
|
||||||
|
"integrity": "sha512-G1LWKhDSvhGeAQ8mPVQlqNcOB2sJdwATtZKl2pDKKHfpf/rYj24lkinxf69blJbnsvtqqNU+L3SL50vzZhXOnw==",
|
||||||
|
"requires": {
|
||||||
|
"isarray": "2.0.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"has-cors": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/has-cors/-/has-cors-1.1.0.tgz",
|
||||||
|
"integrity": "sha1-XkdHk/fqmEPRu5nCPu9J/xJv/zk="
|
||||||
|
},
|
||||||
|
"indexof": {
|
||||||
|
"version": "0.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/indexof/-/indexof-0.0.1.tgz",
|
||||||
|
"integrity": "sha1-gtwzbSMrkGIXnQWrMpOmYFn9Q10="
|
||||||
|
},
|
||||||
|
"isarray": {
|
||||||
|
"version": "2.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.1.tgz",
|
||||||
|
"integrity": "sha1-o32U7ZzaLVmGXJ92/llu4fM4dB4="
|
||||||
|
},
|
||||||
|
"ms": {
|
||||||
|
"version": "2.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
|
||||||
|
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
|
||||||
|
},
|
||||||
|
"object-component": {
|
||||||
|
"version": "0.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/object-component/-/object-component-0.0.3.tgz",
|
||||||
|
"integrity": "sha1-8MaapQ78lbhmwYb0AKM3acsvEpE="
|
||||||
|
},
|
||||||
|
"parseqs": {
|
||||||
|
"version": "0.0.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/parseqs/-/parseqs-0.0.5.tgz",
|
||||||
|
"integrity": "sha1-1SCKNzjkZ2bikbouoXNoSSGouJ0=",
|
||||||
|
"requires": {
|
||||||
|
"better-assert": "~1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"parseuri": {
|
||||||
|
"version": "0.0.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/parseuri/-/parseuri-0.0.5.tgz",
|
||||||
|
"integrity": "sha1-gCBKUNTbt3m/3G6+J3jZDkvOMgo=",
|
||||||
|
"requires": {
|
||||||
|
"better-assert": "~1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"safe-buffer": {
|
||||||
|
"version": "5.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
|
||||||
|
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
|
||||||
|
},
|
||||||
|
"socket.io-client": {
|
||||||
|
"version": "2.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-2.3.0.tgz",
|
||||||
|
"integrity": "sha512-cEQQf24gET3rfhxZ2jJ5xzAOo/xhZwK+mOqtGRg5IowZsMgwvHwnf/mCRapAAkadhM26y+iydgwsXGObBB5ZdA==",
|
||||||
|
"requires": {
|
||||||
|
"backo2": "1.0.2",
|
||||||
|
"base64-arraybuffer": "0.1.5",
|
||||||
|
"component-bind": "1.0.0",
|
||||||
|
"component-emitter": "1.2.1",
|
||||||
|
"debug": "~4.1.0",
|
||||||
|
"engine.io-client": "~3.4.0",
|
||||||
|
"has-binary2": "~1.0.2",
|
||||||
|
"has-cors": "1.1.0",
|
||||||
|
"indexof": "0.0.1",
|
||||||
|
"object-component": "0.0.3",
|
||||||
|
"parseqs": "0.0.5",
|
||||||
|
"parseuri": "0.0.5",
|
||||||
|
"socket.io-parser": "~3.3.0",
|
||||||
|
"to-array": "0.1.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"socket.io-parser": {
|
||||||
|
"version": "3.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-3.3.0.tgz",
|
||||||
|
"integrity": "sha512-hczmV6bDgdaEbVqhAeVMM/jfUfzuEZHsQg6eOmLgJht6G3mPKMxYm75w2+qhAQZ+4X+1+ATZ+QFKeOZD5riHng==",
|
||||||
|
"requires": {
|
||||||
|
"component-emitter": "1.2.1",
|
||||||
|
"debug": "~3.1.0",
|
||||||
|
"isarray": "2.0.1"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"debug": {
|
||||||
|
"version": "3.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
|
||||||
|
"integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
|
||||||
|
"requires": {
|
||||||
|
"ms": "2.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ms": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
|
||||||
|
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"to-array": {
|
||||||
|
"version": "0.1.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/to-array/-/to-array-0.1.4.tgz",
|
||||||
|
"integrity": "sha1-F+bBH3PdTz10zaek/zI46a2b+JA="
|
||||||
|
},
|
||||||
|
"ultron": {
|
||||||
|
"version": "1.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/ultron/-/ultron-1.1.1.tgz",
|
||||||
|
"integrity": "sha512-UIEXBNeYmKptWH6z8ZnqTeS8fV74zG0/eRU9VGkpzz+LIJNs8W/zM/L+7ctCkRrgbNnnR0xxw4bKOr0cW0N0Og=="
|
||||||
|
},
|
||||||
|
"weapp.engine.io-client": {
|
||||||
|
"version": "3.2.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/weapp.engine.io-client/-/weapp.engine.io-client-3.2.3.tgz",
|
||||||
|
"integrity": "sha512-PDqfZG4o4m7uvDAEUg7DLV0OZrj2mWZglq5W3I7K7tFFSfyvhrFa2yVRQIE6HfIZgq+Z1YhAO1wLg1mKT3JH9g==",
|
||||||
|
"requires": {
|
||||||
|
"component-emitter": "1.2.1",
|
||||||
|
"component-inherit": "0.0.3",
|
||||||
|
"debug": "~3.1.0",
|
||||||
|
"engine.io-parser": "~2.1.1",
|
||||||
|
"has-cors": "1.1.0",
|
||||||
|
"indexof": "0.0.1",
|
||||||
|
"parseqs": "0.0.5",
|
||||||
|
"parseuri": "0.0.5",
|
||||||
|
"ws": "~3.3.1",
|
||||||
|
"xmlhttprequest-ssl": "~1.5.4",
|
||||||
|
"yeast": "0.1.2"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"debug": {
|
||||||
|
"version": "3.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
|
||||||
|
"integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
|
||||||
|
"requires": {
|
||||||
|
"ms": "2.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"engine.io-parser": {
|
||||||
|
"version": "2.1.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-2.1.3.tgz",
|
||||||
|
"integrity": "sha512-6HXPre2O4Houl7c4g7Ic/XzPnHBvaEmN90vtRO9uLmwtRqQmTOw0QMevL1TOfL2Cpu1VzsaTmMotQgMdkzGkVA==",
|
||||||
|
"requires": {
|
||||||
|
"after": "0.8.2",
|
||||||
|
"arraybuffer.slice": "~0.0.7",
|
||||||
|
"base64-arraybuffer": "0.1.5",
|
||||||
|
"blob": "0.0.5",
|
||||||
|
"has-binary2": "~1.0.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ms": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
|
||||||
|
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
|
||||||
|
},
|
||||||
|
"ws": {
|
||||||
|
"version": "3.3.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/ws/-/ws-3.3.3.tgz",
|
||||||
|
"integrity": "sha512-nnWLa/NwZSt4KQJu51MYlCcSQ5g7INpOrOMt4XV8j4dqTXdmlUmSHQ8/oLC069ckre0fRsgfvsKwbTdtKLCDkA==",
|
||||||
|
"requires": {
|
||||||
|
"async-limiter": "~1.0.0",
|
||||||
|
"safe-buffer": "~5.1.0",
|
||||||
|
"ultron": "~1.1.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"weapp.socket.io": {
|
||||||
|
"version": "2.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/weapp.socket.io/-/weapp.socket.io-2.0.1.tgz",
|
||||||
|
"integrity": "sha512-NzWCOa3zzMo70RuV61Mm4ic68r7jV4xLplx9cnTVJ/K9ecMjuA4ANgWRlB3U1ww6kyOyYnbDC2o2umpJFM3thQ==",
|
||||||
|
"requires": {
|
||||||
|
"socket.io-client": "^2.1.1",
|
||||||
|
"weapp.engine.io-client": "^3.2.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ws": {
|
||||||
|
"version": "6.1.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/ws/-/ws-6.1.4.tgz",
|
||||||
|
"integrity": "sha512-eqZfL+NE/YQc1/ZynhojeV8q+H050oR8AZ2uIev7RU10svA9ZnJUddHcOUZTJLinZ9yEfdA2kSATS2qZK5fhJA==",
|
||||||
|
"requires": {
|
||||||
|
"async-limiter": "~1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"xmlhttprequest-ssl": {
|
||||||
|
"version": "1.5.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.5.5.tgz",
|
||||||
|
"integrity": "sha1-wodrBhaKrcQOV9l+gRkayPQ5iz4="
|
||||||
|
},
|
||||||
|
"yeast": {
|
||||||
|
"version": "0.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/yeast/-/yeast-0.1.2.tgz",
|
||||||
|
"integrity": "sha1-AI4G2AlDIMNy28L47XagymyKxBk="
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
5
UniAppMobile/package.json
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"dependencies": {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
28
UniAppMobile/pages.json
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
{
|
||||||
|
"pages": [ //pages数组中第一项表示应用启动页,参考:https://uniapp.dcloud.io/collocation/pages
|
||||||
|
{
|
||||||
|
"path": "pages/index/index",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "移动端扫码登录"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path" : "pages/login/login",
|
||||||
|
"style" : {
|
||||||
|
"navigationBarTitleText": "确认登录"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
,{
|
||||||
|
"path" : "pages/authorization/authorization",
|
||||||
|
"style" : {
|
||||||
|
"navigationBarTitleText": "移动端授权登录"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"globalStyle": {
|
||||||
|
"navigationBarTextStyle": "black",
|
||||||
|
"navigationBarTitleText": "uni-app",
|
||||||
|
"navigationBarBackgroundColor": "#F8F8F8",
|
||||||
|
"backgroundColor": "#F8F8F8"
|
||||||
|
}
|
||||||
|
}
|
||||||
1
UniAppMobile/pages/.pydio
Normal file
@@ -0,0 +1 @@
|
|||||||
|
3e252970-c241-4b5f-a964-2b49c8802b47
|
||||||
1
UniAppMobile/pages/authorization/.pydio
Normal file
@@ -0,0 +1 @@
|
|||||||
|
c9b89ada-5572-47b3-9ed2-1ae864de57d5
|
||||||
59
UniAppMobile/pages/authorization/authorization.vue
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
<template>
|
||||||
|
<view class="authorization">
|
||||||
|
<h2>授权登录</h2>
|
||||||
|
<button type="primary" @click="AuthorizationLogin">授权Web登录</button>
|
||||||
|
<p class="tips">{{tips}}</p>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
tips: null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
AuthorizationLogin() {
|
||||||
|
console.log("授权Web登录");
|
||||||
|
uni.request({
|
||||||
|
url: `${this.$config.baseUrl}/AuthorizedWeb`,
|
||||||
|
method: 'GET',
|
||||||
|
data: {
|
||||||
|
uid: uni.getStorageSync('userinfo').id
|
||||||
|
},
|
||||||
|
success: (res) => {
|
||||||
|
if (res.data.status == 404) {
|
||||||
|
uni.showToast({
|
||||||
|
title: res.data.msg,
|
||||||
|
icon: 'none',
|
||||||
|
duration: 2000
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
uni.showToast({
|
||||||
|
title: res.data.msg,
|
||||||
|
icon: 'none',
|
||||||
|
duration: 3000
|
||||||
|
});
|
||||||
|
this.tips = res.data.msg
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.authorization {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
padding: 20% 0;
|
||||||
|
text-align: center;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
h2 {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
1
UniAppMobile/pages/index/.pydio
Normal file
@@ -0,0 +1 @@
|
|||||||
|
f3d71557-f4a9-4a7c-b077-e36d352dbb4e
|
||||||
106
UniAppMobile/pages/index/index.vue
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
<template>
|
||||||
|
|
||||||
|
<div class="scanCode">
|
||||||
|
<h3 class="title">扫码登录 案例</h3>
|
||||||
|
<p>Nodejs + Redis + Mysql + Vue + Uni-app</p>
|
||||||
|
<button class="scan" type="primary" @click="scanCode">扫码登录</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
uuid: null,
|
||||||
|
StorageSync: null,
|
||||||
|
timer: null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onLoad() {
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
this.setTime();
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
setTime() {
|
||||||
|
this.timer = setInterval(() => {
|
||||||
|
this.CheckAuthorization();
|
||||||
|
},1000);
|
||||||
|
},
|
||||||
|
// 定时器,每隔一秒检查web端是否在请求登录
|
||||||
|
CheckAuthorization() {
|
||||||
|
uni.request({
|
||||||
|
url: `${this.$config.baseUrl}/InquiryWebIsLogining`,
|
||||||
|
method: 'GET',
|
||||||
|
data: {
|
||||||
|
uid: uni.getStorageSync('userinfo').id
|
||||||
|
},
|
||||||
|
success: (res) => {
|
||||||
|
switch (res.data.status){
|
||||||
|
// 没有对应的web端请求登录
|
||||||
|
case 404:
|
||||||
|
console.log(res.data.msg)
|
||||||
|
break;
|
||||||
|
// web端正在请求授权
|
||||||
|
case 200:
|
||||||
|
clearInterval(this.timer)
|
||||||
|
uni.navigateTo({
|
||||||
|
url: `/pages/authorization/authorization`
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
// 授权成功,请勿重复授权
|
||||||
|
case 500:
|
||||||
|
uni.showToast({
|
||||||
|
title: res.data.msg,
|
||||||
|
icon: 'none',
|
||||||
|
duration: 2000
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
scanCode() {
|
||||||
|
uni.scanCode({
|
||||||
|
success: (res) => {
|
||||||
|
// 保存扫码获得的uuid
|
||||||
|
this.uuid = res.result;
|
||||||
|
uni.request({
|
||||||
|
url: `${this.$config.baseUrl}/SweepStatus`,
|
||||||
|
method: 'GET',
|
||||||
|
data: {
|
||||||
|
uuid: this.uuid
|
||||||
|
},
|
||||||
|
success: (res) => {
|
||||||
|
console.log("ddddddd ------: ", res)
|
||||||
|
uni.navigateTo({
|
||||||
|
url: `/pages/login/login?uuid=${this.uuid}`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.scanCode {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 20% 0;
|
||||||
|
}
|
||||||
|
p {
|
||||||
|
color: #8d8d8d;
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
.scan {
|
||||||
|
margin-top: 30px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
1
UniAppMobile/pages/login/.pydio
Normal file
@@ -0,0 +1 @@
|
|||||||
|
6e7a3a19-b3de-4452-9d21-1dee74cb05a3
|
||||||
64
UniAppMobile/pages/login/login.vue
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
<template>
|
||||||
|
<div class="login">
|
||||||
|
<h2>确认登录吗</h2>
|
||||||
|
<h2 v-if="loginSuccess">登录成功</h2>
|
||||||
|
<button type="primary" @click="submitLogin">确 认</button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
uuid: "",
|
||||||
|
loginSuccess: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onLoad(option) {
|
||||||
|
this.uuid = option.uuid;
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
submitLogin() {
|
||||||
|
uni.request({
|
||||||
|
url: `${this.$config.baseUrl}/login`,
|
||||||
|
method: 'GET',
|
||||||
|
data: {
|
||||||
|
uuid: this.uuid,
|
||||||
|
uid: uni.getStorageSync('userinfo').id
|
||||||
|
},
|
||||||
|
success: (res) => {
|
||||||
|
if (res.data.status == 404) {
|
||||||
|
uni.showToast({
|
||||||
|
title: res.data.msg,
|
||||||
|
icon: 'none',
|
||||||
|
duration: 2000
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
uni.showToast({
|
||||||
|
title: res.data.msg,
|
||||||
|
icon: 'none',
|
||||||
|
duration: 3000
|
||||||
|
});
|
||||||
|
this.loginSuccess = true
|
||||||
|
}
|
||||||
|
console.log("res ------: ", JSON.stringify(res));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.login {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
padding: 20% 0;
|
||||||
|
text-align: center;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
h2 {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
1
UniAppMobile/static/.pydio
Normal file
@@ -0,0 +1 @@
|
|||||||
|
a7ed3376-db93-4230-8c23-a6b1d3dc880e
|
||||||
BIN
UniAppMobile/static/logo.png
Normal file
|
After Width: | Height: | Size: 3.9 KiB |
76
UniAppMobile/uni.scss
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
/**
|
||||||
|
* 这里是uni-app内置的常用样式变量
|
||||||
|
*
|
||||||
|
* uni-app 官方扩展插件及插件市场(https://ext.dcloud.net.cn)上很多三方插件均使用了这些样式变量
|
||||||
|
* 如果你是插件开发者,建议你使用scss预处理,并在插件代码中直接使用这些变量(无需 import 这个文件),方便用户通过搭积木的方式开发整体风格一致的App
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 如果你是App开发者(插件使用者),你可以通过修改这些变量来定制自己的插件主题,实现自定义主题功能
|
||||||
|
*
|
||||||
|
* 如果你的项目同样使用了scss预处理,你也可以直接在你的 scss 代码中使用如下变量,同时无需 import 这个文件
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* 颜色变量 */
|
||||||
|
|
||||||
|
/* 行为相关颜色 */
|
||||||
|
$uni-color-primary: #007aff;
|
||||||
|
$uni-color-success: #4cd964;
|
||||||
|
$uni-color-warning: #f0ad4e;
|
||||||
|
$uni-color-error: #dd524d;
|
||||||
|
|
||||||
|
/* 文字基本颜色 */
|
||||||
|
$uni-text-color:#333;//基本色
|
||||||
|
$uni-text-color-inverse:#fff;//反色
|
||||||
|
$uni-text-color-grey:#999;//辅助灰色,如加载更多的提示信息
|
||||||
|
$uni-text-color-placeholder: #808080;
|
||||||
|
$uni-text-color-disable:#c0c0c0;
|
||||||
|
|
||||||
|
/* 背景颜色 */
|
||||||
|
$uni-bg-color:#ffffff;
|
||||||
|
$uni-bg-color-grey:#f8f8f8;
|
||||||
|
$uni-bg-color-hover:#f1f1f1;//点击状态颜色
|
||||||
|
$uni-bg-color-mask:rgba(0, 0, 0, 0.4);//遮罩颜色
|
||||||
|
|
||||||
|
/* 边框颜色 */
|
||||||
|
$uni-border-color:#c8c7cc;
|
||||||
|
|
||||||
|
/* 尺寸变量 */
|
||||||
|
|
||||||
|
/* 文字尺寸 */
|
||||||
|
$uni-font-size-sm:24upx;
|
||||||
|
$uni-font-size-base:28upx;
|
||||||
|
$uni-font-size-lg:32upx;
|
||||||
|
|
||||||
|
/* 图片尺寸 */
|
||||||
|
$uni-img-size-sm:40upx;
|
||||||
|
$uni-img-size-base:52upx;
|
||||||
|
$uni-img-size-lg:80upx;
|
||||||
|
|
||||||
|
/* Border Radius */
|
||||||
|
$uni-border-radius-sm: 4upx;
|
||||||
|
$uni-border-radius-base: 6upx;
|
||||||
|
$uni-border-radius-lg: 12upx;
|
||||||
|
$uni-border-radius-circle: 50%;
|
||||||
|
|
||||||
|
/* 水平间距 */
|
||||||
|
$uni-spacing-row-sm: 10px;
|
||||||
|
$uni-spacing-row-base: 20upx;
|
||||||
|
$uni-spacing-row-lg: 30upx;
|
||||||
|
|
||||||
|
/* 垂直间距 */
|
||||||
|
$uni-spacing-col-sm: 8upx;
|
||||||
|
$uni-spacing-col-base: 16upx;
|
||||||
|
$uni-spacing-col-lg: 24upx;
|
||||||
|
|
||||||
|
/* 透明度 */
|
||||||
|
$uni-opacity-disabled: 0.3; // 组件禁用态的透明度
|
||||||
|
|
||||||
|
/* 文章场景相关 */
|
||||||
|
$uni-color-title: #2C405A; // 文章标题颜色
|
||||||
|
$uni-font-size-title:40upx;
|
||||||
|
$uni-color-subtitle: #555555; // 二级标题颜色
|
||||||
|
$uni-font-size-subtitle:36upx;
|
||||||
|
$uni-color-paragraph: #3F536E; // 文章段落颜色
|
||||||
|
$uni-font-size-paragraph:30upx;
|
||||||
1
VueQRCode/.pydio
Normal file
@@ -0,0 +1 @@
|
|||||||
|
5871485d-34e9-410d-b06c-e5b04078fa6d
|
||||||
1
VueQRCode/img/.pydio
Normal file
@@ -0,0 +1 @@
|
|||||||
|
16eac697-009f-45a6-b5de-63e2dddc5f59
|
||||||
BIN
VueQRCode/img/reload.png
Normal file
|
After Width: | Height: | Size: 3.3 KiB |
BIN
VueQRCode/img/success.png
Normal file
|
After Width: | Height: | Size: 3.6 KiB |
278
VueQRCode/index.html
Normal file
@@ -0,0 +1,278 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>Title</title>
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
|
||||||
|
<script src="js/socket.io.js"></script>
|
||||||
|
<link rel="stylesheet" href="https://unpkg.com/element-ui/lib/theme-chalk/index.css">
|
||||||
|
<script src="https://unpkg.com/element-ui/lib/index.js"></script>
|
||||||
|
<style>
|
||||||
|
* {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
list-style: none;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
html,
|
||||||
|
body,
|
||||||
|
#app {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
#app {
|
||||||
|
margin: 0 auto;
|
||||||
|
text-align: center;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
.title {
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
.QRcode {
|
||||||
|
margin-top: 20px;
|
||||||
|
width: 240px;
|
||||||
|
height: 240px;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
.QRcode_overtime {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
flex-direction: column;
|
||||||
|
position: absolute;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
background: rgba(0, 0, 0, 0.68);
|
||||||
|
color: #fff;
|
||||||
|
text-align: center;
|
||||||
|
z-index: 2;
|
||||||
|
font-size: 18px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.QRcode_overtime img {
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
margin-top: 15px;
|
||||||
|
}
|
||||||
|
.QRcode_success {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
flex-direction: column;
|
||||||
|
position: absolute;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
background: rgba(0, 0, 0, 0.68);
|
||||||
|
color: #fff;
|
||||||
|
text-align: center;
|
||||||
|
z-index: 3;
|
||||||
|
font-size: 18px;
|
||||||
|
}
|
||||||
|
.QRcode_success img {
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
}
|
||||||
|
.QRcode_img {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
position: absolute;
|
||||||
|
z-index: 1;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
}
|
||||||
|
.info {
|
||||||
|
color: #8d8d8d;
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
.tips {
|
||||||
|
margin-top: 10px;
|
||||||
|
background: #8d8d8d;
|
||||||
|
border-radius: 24px;
|
||||||
|
padding: 4px 14px;
|
||||||
|
font-size: 13px;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
.WapEmpower button {
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
.tips {
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<div id="app">
|
||||||
|
|
||||||
|
<a href='https://gitee.com/bmycode/ScanCodeLogin'>
|
||||||
|
<img src='https://gitee.com/bmycode/ScanCodeLogin/widgets/widget_3.svg' alt='Fork me on Gitee'></img>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<h2 class="title">扫码登录 案例</h2>
|
||||||
|
<p class="info">Nodejs + Redis + Socket + Mysql + Vue + Uni-app</p>
|
||||||
|
<br>
|
||||||
|
<el-tabs v-model="activeName" @tab-click="handleClick">
|
||||||
|
<el-tab-pane label="移动端扫码登录" name="ScanCode">
|
||||||
|
<div class="QRcode">
|
||||||
|
<!--扫码成功的样式-->
|
||||||
|
<div class="QRcode_success" v-if="successShow">
|
||||||
|
<img src="./img/success.png" alt="success">
|
||||||
|
<p>扫码成功</p>
|
||||||
|
</div>
|
||||||
|
<!--二维码失效的样式-->
|
||||||
|
<div class="QRcode_overtime" @click="AgainScanCode" v-if="overtimeShow">
|
||||||
|
<p>二维码失效,点击重新获取</p>
|
||||||
|
<img src="./img/reload.png" alt="reload.png">
|
||||||
|
</div>
|
||||||
|
<!--真正让用户扫的二维码-->
|
||||||
|
<img class="QRcode_img" :src="base64QRcode.QRcode">
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<p class="tips">{{ tipsText }}</p>
|
||||||
|
</el-tab-pane>
|
||||||
|
<el-tab-pane label="移动端授权登录" name="WapEmpower">
|
||||||
|
<div class="WapEmpower">
|
||||||
|
<p>请点击【登录】按钮</p>
|
||||||
|
<p>在移动端弹窗的页面中授权本次登录</p>
|
||||||
|
<p class="tips" v-if="WapSuccess.length != 0">{{WapSuccess}}</p>
|
||||||
|
<el-button type="success" @click="RequestLogin"> 登 录 </el-button>
|
||||||
|
</div>
|
||||||
|
</el-tab-pane>
|
||||||
|
</el-tabs>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
var app = new Vue({
|
||||||
|
el: '#app',
|
||||||
|
data: {
|
||||||
|
socket: null, // 保存 socket链接
|
||||||
|
socketURL: 'http://192.168.0.133:9080', // socket 服务的地址
|
||||||
|
base64QRcode: '', // 保存后端给的二维码和uuid
|
||||||
|
timerCheck: null, // 保存定时器
|
||||||
|
tipsText: '请扫码', // 提示文字
|
||||||
|
overtimeShow: false, // 是否显示二维码失效
|
||||||
|
successShow: false, // 是否显示扫码成功
|
||||||
|
ScanCodeShow: false, // 如果用户扫码了,那么不显示 等待用户扫码中 的提示文字
|
||||||
|
activeName: 'ScanCode',// 保存当前tab的类型
|
||||||
|
WapSuccess: '' // 授权登录成功后的提示信息
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
// 创建 Socket 链接
|
||||||
|
this.socket = io.connect(this.socketURL);
|
||||||
|
this.handleClick();
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
// 判断点击的是哪个tabs,默认激活调用 【移动端扫码登录】
|
||||||
|
handleClick() {
|
||||||
|
switch (this.activeName) {
|
||||||
|
case "ScanCode":
|
||||||
|
this.getQRcode();
|
||||||
|
this.ScanCodeOvertime();
|
||||||
|
this.ScanCodeing();
|
||||||
|
break;
|
||||||
|
case "WapEmpower":
|
||||||
|
this.DoNotRepeatRequest();
|
||||||
|
this.AuthorizedWebSuccess()
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// 请求移动端登录按钮被点击,检查用户是否已经扫码登录,有发送emit,没有提示
|
||||||
|
RequestLogin() {
|
||||||
|
localStorage.getItem("id") !== null
|
||||||
|
? this.socket.emit('WebRequestLogin', localStorage.getItem("id"))
|
||||||
|
: this.$notify.error({ title: '提示', message: '请先扫码登录哦!' });
|
||||||
|
},
|
||||||
|
// 监听后端 emit, 重复点击 登录 按钮的提示
|
||||||
|
DoNotRepeatRequest() {
|
||||||
|
this.socket.on('DoNotRepeatRequest', res => {
|
||||||
|
this.$notify.error({
|
||||||
|
title: '提示',
|
||||||
|
message: res
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
// 移动端成功授权web端登录的监听
|
||||||
|
AuthorizedWebSuccess() {
|
||||||
|
this.socket.on('AuthorizedWebSuccess', res => {
|
||||||
|
this.WapSuccess = res
|
||||||
|
console.log("移动端授权登录成功,用户信息为:", res)
|
||||||
|
});
|
||||||
|
},
|
||||||
|
getQRcode() {
|
||||||
|
// 向服务器要uuid和二维码
|
||||||
|
this.socket.emit('getQRcode');
|
||||||
|
this.sendQRcode();
|
||||||
|
},
|
||||||
|
// 接受服务器给的uuid和二维码
|
||||||
|
sendQRcode() {
|
||||||
|
this.socket.on('sendQRcode', res => this.base64QRcode = res);
|
||||||
|
this.checkScanCode();
|
||||||
|
},
|
||||||
|
checkScanCode() {
|
||||||
|
|
||||||
|
// 等待扫码
|
||||||
|
this.socket.on('waitScanCode', res => {
|
||||||
|
if (!this.ScanCodeShow) {
|
||||||
|
this.tipsText = '等待用户扫码'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 扫码成功
|
||||||
|
this.socket.on('SuccessScanCode', (res) => {
|
||||||
|
localStorage.setItem("id", JSON.parse(res).id);
|
||||||
|
//this.tipsText = `扫码成功用户信息为:${res}`; // 提示信息
|
||||||
|
this.successShow = true; // 显示扫码成功的样式
|
||||||
|
clearInterval(this.timerCheck); // 扫码成功,清除定时器,不再询问服务器
|
||||||
|
});
|
||||||
|
|
||||||
|
// 每1秒询问一次服务器,用户App端是否扫码
|
||||||
|
this.timerCheck = setInterval(() => this.socket.emit("checkScanCode", this.base64QRcode.uuid),1000);
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
// 如果二维码超时
|
||||||
|
ScanCodeOvertime() {
|
||||||
|
this.socket.on('ScanCodeOvertime', () => {
|
||||||
|
// 如果二维码已经失效,并且用户还未扫码的话,那么才去显示二维码失效的样式
|
||||||
|
// 之所以在这判断一次的原因,因为如果用户扫码成功之后,二维码才失效
|
||||||
|
// 页面上会显示二维码失效的样式,体验不好。
|
||||||
|
if(!this.successShow) {
|
||||||
|
console.log("【Vue端】:二维码超时");
|
||||||
|
clearInterval(this.timerCheck); // 二维码失效,不再询问服务器
|
||||||
|
this.overtimeShow = true; // 显示二维码失效的样式
|
||||||
|
this.tipsText = "二维码失效"; // 清空等待扫码文字
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
// 已扫码但未确认
|
||||||
|
ScanCodeing() {
|
||||||
|
this.socket.on('ScanCodeing', () => {
|
||||||
|
this.ScanCodeShow = true;
|
||||||
|
this.tipsText = "您已扫码,请在手机上确认登录!"
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
// 二维码失效后,用户点击二维码重新获取二维码的事件
|
||||||
|
AgainScanCode() {
|
||||||
|
this.overtimeShow = false;
|
||||||
|
this.ScanCodeShow = false;
|
||||||
|
this.socket.emit('getQRcode');
|
||||||
|
this.sendQRcode();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
</html>
|
||||||
1
VueQRCode/js/.pydio
Normal file
@@ -0,0 +1 @@
|
|||||||
|
5603e8bb-6ec9-4f4b-89f5-b99e50f0be03
|
||||||