提交 397b7187 编写于 作者: yu's avatar yu

登录注册 - 前后端

房间创建、加入、退出和消息发布 - 服务端
上级 ea0ec39c
{ // launch.json 配置了启动调试时相关设置,configurations下节点名称可为 app-plus/h5/mp-weixin/mp-baidu/mp-alipay/mp-qq/mp-toutiao/mp-360/
// launchtype项可配置值为local或remote, local代表前端连本地云函数,remote代表前端连云端云函数
"version": "0.0",
"configurations": [{
"default" :
{
"launchtype" : "local"
},
"h5" :
{
"launchtype" : "local"
},
"type" : "uniCloud"
}
]
}
<script>
import { ddp } from "./modules/core/ddp";
ddp.subscribe("rooms.all")
ddp.subscribe("rooms.mine")
export default {
onLaunch: function () {
console.log("App Launch");
},
onShow: function () {
console.log("App Show");
},
onHide: function () {
console.log("App Hide");
},
};
</script>
<style>
/*每个页面公共css */
page {
display: flex;
flex-direction: column;
/*
min-height: 100%; */
background-color: #eeeeff;
min-height: 100%;
}
page-body {
background-color: red;
}
.frow,
.fcol {
display: flex;
}
.frow {
flex-direction: row;
}
.fcol {
flex-direction: column;
}
.fcnt,
.cntx {
justify-content: center;
}
.fcnt,
.cnty {
align-items: center;
}
.fbtw {
justify-content: space-between;
}
.fsc {
align-self: center;
}
.fse {
align-self: flex-end;
}
.fss {
align-self: flex-start;
}
.pd {
padding: 0.5rem;
}
.pdh {
padding: 0.5rem 0;
}
.f1 {
flex: 1;
}
.pdv {
padding: 0 0.5rem;
}
.mg {
margin: 0.5rem;
}
.mgh {
margin: 0.5rem 0;
}
.mgv {
margin: 0 0.5rem;
}
.mgt {
margin-top: 0.5rem;
}
.mgb {
margin-bottom: 0.5rem;
}
</style>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0" />
<title></title>
<!--preload-links-->
<!--app-context-->
</head>
<body>
<div id="app"><!--app-html--></div>
<script type="module" src="/main.js"></script>
</body>
</html>
import App from './App'
// #ifndef VUE3
import Vue from 'vue'
Vue.config.productionTip = false
App.mpType = 'app'
const app = new Vue({
...App
})
app.$mount()
// #endif
// #ifdef VUE3
import { createSSRApp } from 'vue'
export function createApp() {
const app = createSSRApp(App)
return {
app
}
}
// #endif
\ No newline at end of file
{
"name" : "dpp-demo",
"appid" : "__UNI__FB8BA41",
"description" : "",
"versionName" : "1.0.0",
"versionCode" : "100",
"transformPx" : false,
/* 5+App特有相关 */
"app-plus" : {
"usingComponents" : true,
"nvueStyleCompiler" : "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.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.ACCESS_NETWORK_STATE\"/>",
"<uses-permission android:name=\"android.permission.CAMERA\"/>",
"<uses-permission android:name=\"android.permission.GET_ACCOUNTS\"/>",
"<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.FLASHLIGHT\"/>",
"<uses-feature android:name=\"android.hardware.camera\"/>",
"<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
},
"uniStatistics" : {
"enable" : false
},
"vueVersion" : "3"
}
node_modules/
bundle/
.meteor/*
\ No newline at end of file
此差异已折叠。
{
"name": "app",
"private": true,
"scripts": {
"start": "meteor run --port 0.0.0.0:3002",
"test": "meteor test --once --driver-package meteortesting:mocha",
"test-app": "TEST_WATCH=1 meteor test --full-app --driver-package meteortesting:mocha",
"visualize": "meteor --production --extra-packages bundle-visualizer",
"build": "cross-env NODE_ENV=development meteor build --debug --directory ../ --platforms web.browser",
"remote-db": "SET MONGO_URL=mongodb://xxxxx && npm run start"
},
"dependencies": {
"@babel/runtime": "^7.14.8",
"meteor-node-stubs": "^1.1.0"
},
"meteor": {
"mainModule": {
"server": "server/main.ts"
},
"testModule": "tests/main.js"
},
"devDependencies": {
"@types/meteor": "^1.4.78",
"@types/meteor-publish-composite": "0.0.37",
"@types/mocha": "^9.0.0",
"cross-env": "^7.0.3",
"typescript": "^4.4.2"
}
}
import { Meteor } from "meteor/meteor";
import { Mongo } from "meteor/mongo";
import { publishComposite } from "meteor/reywood:publish-composite";
Meteor.startup(() => {
console.log(`Hi boot! -- ddp demo -- oh yeah -`);
});
const Users = Meteor.users;
const Rooms = new Mongo.Collection<any>("rooms");
const Messages = new Mongo.Collection<any>("messages");
Meteor.methods({
"message.add": function (data: any) {
if (!this.userId || !data.room)
throw new Meteor.Error("仅有登录用户能那啥");
return Messages.insert({
...data,
user: this.userId,
});
},
"room.create": function (name: string) {
if (!this.userId) throw new Meteor.Error("仅有登录用户能那啥");
return Rooms.insert({
name,
createdBy: this.userId,
createdAt: Date.now(),
members: [this.userId],
});
},
"room.join": function (id: string) {
if (!this.userId) throw new Meteor.Error("仅有登录用户能那啥");
const room = Rooms.findOne(id);
if (!room) throw new Meteor.Error("没房间不能那啥");
return Rooms.update(id, {
$addToSet: {
members: this.userId,
},
});
},
"room.left": function (id: string) {
if (!this.userId) throw new Meteor.Error("仅有登录用户能那啥");
const room = Rooms.findOne(id);
if (!room) throw new Meteor.Error("没房间不能那啥");
return Rooms.update(id, {
$pull: {
members: this.userId,
},
});
}
});
Meteor.publish("rooms.all", function () {
return Rooms.find({}, { fields: { name: 1, _id: 1 } });
});
publishComposite("rooms.mine", function () {
const userId = this.userId;
return {
find() {
return Rooms.find(
{ members: userId },
{ fields: { name: 1, members: 1 } }
);
},
collectionName: `rooms-mine`,
children: [
{
find(room) {
return Users.find(
{ id: { $in: room.membsers } },
{ fields: { profile: 1 } }
);
},
},
{
find(room) {
return Messages.find({ roomId: room._id });
},
},
],
};
});
Meteor.onConnection((con) => {
console.log(`${con.id} connected`);
con.onClose(() => {
console.log(`${con.id} closed`);
});
});
import assert from "assert";
describe("app", function () {
it("package.json has correct name", async function () {
const { name } = await import("../package.json");
assert.strictEqual(name, "app");
});
if (Meteor.isClient) {
it("client is not server", function () {
assert.strictEqual(Meteor.isServer, false);
});
}
if (Meteor.isServer) {
it("server is not client", function () {
assert.strictEqual(Meteor.isClient, false);
});
}
});
{
"compilerOptions": {
/* Basic Options */
"target": "es2018",
"module": "esNext",
"lib": ["esnext", "dom"],
"allowJs": true,
"checkJs": false,
"jsx": "preserve",
"incremental": true,
"noEmit": true,
/* Strict Type-Checking Options */
"strict": true,
"noImplicitAny": true,
"strictNullChecks": true,
/* Additional Checks */
"noUnusedLocals": true,
"noUnusedParameters": true,
"noImplicitReturns": false,
"noFallthroughCasesInSwitch": false,
/* Module Resolution Options */
"baseUrl": ".",
"paths": {
/* Support absolute /imports/* with a leading '/' */
"/*": ["*"]
},
"moduleResolution": "node",
"resolveJsonModule": true,
"types": ["node", "mocha","meteor","meteor-publish-composite",],
"esModuleInterop": true,
"preserveSymlinks": true
},
"exclude": [
"./.meteor/**",
"./packages/**"
],
"include": [
"server/**/*.ts",
"server/**/*.d.ts",
"imports/**/*.ts",
"imports/**/*.d.ts",
"../shared/**/*.ts",
"../shared/**/*.d.ts"
]
}
import { init } from "../../uni_modules/hj-ddp/js_sdk";
import { hjMeteorAccount } from "../../uni_modules/hj-meteor-account/js_sdk";
// #ifdef H5
export const ddp = init('ws://localhost:3002')
// #endif
// #ifndef H5
export const ddp = init('ws://10.0.2.2:3002')
// #endif
ddp.use(hjMeteorAccount)
\ No newline at end of file
<template>
<view class="fcol cntx f1">
<view class="input-list">
<view class="list-call">
<input v-model="data.username" type="text" maxlength="11" placeholder="请输入用户名" />
</view>
<view class="list-call">
<input v-model="data.password" type="text" maxlength="32" placeholder="输入密码" password="true" />
</view>
</view>
<view class="dlbutton fsc" hover-class="dlbutton-hover" @tap="signin(data)">
<text>登录</text>
</view>
<navigator url="../signup/signup" class="btn mgh fsc">
<button type="default">注册</button>
</navigator>
</view>
</template>
<script setup>
import {
reactive,
watch
} from "vue"
import {
signin,
user
} from "../service";
const data = reactive({
username: "",
password: ""
})
watch(user, (ov, nv) => {
console.log({ov,nv})
if (nv?._id) {
uni.reLaunch({
url: '/pages/index/index'
});
}
}, {
imediate: true
})
</script>
<style scoped="" lang="scss">
.content {
display: flex;
flex-direction: column;
justify-content: center;
}
.input-list {
padding: 50upx 70upx;
}
.list-call {
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
height: 100upx;
color: #333333;
border-bottom: 1upx solid rgba(230, 230, 230, 1);
input {
width: 100%;
}
}
.btn,.dlbutton{
font-size: 34upx;
width: 470upx;
height: 100upx;
border-radius: 50upx;
line-height: 100upx;
}
.dlbutton {
color: #ffffff;
background: linear-gradient(-90deg,
rgba(63, 205, 235, 1),
rgba(188, 226, 158, 1));
box-shadow: 0upx 0upx 13upx 0upx rgba(164, 217, 228, 0.2);
text-align: center;
margin-top: 100upx;
}
.dlbutton-hover {
background: linear-gradient(-90deg,
rgba(63, 205, 235, 0.9),
rgba(188, 226, 158, 0.9));
}
</style>
import {
reactive
} from "vue";
import {
ddp
} from "../../core/ddp";
export const user = reactive({})
export const myRooms = reactive([])
ddp.user.onChange((info) => {
if (info) {
for (const key in info) {
user[key] = info[key]
}
} else {
for (const key in user) {
delete user[key]
}
}
})
export const signin = async (data) => {
if (!data.username || !data.password) return
ddp.loginWithPassword(data.username, data.password).catch(err => {
uni.showToast({
title: '密码或者用户名错误'
})
});
};
export const signup = async (data) => {
if (!data.username || !data.password || data.password !== data.password1) return
ddp.createAccount(data.username, data.password).catch(err => {
uni.showToast({
title: '注册失败'
})
});
};
\ No newline at end of file
<template>
<view class="fcol cntx">
<view class="flex-col input-list">
<view class="list-call">
<input v-model="data.username" type="text" maxlength="11" placeholder="请输入用户名" />
</view>
<view class="list-call">
<input v-model="data.password" type="text" maxlength="32" placeholder="输入密码" password="true" />
</view>
<view class="list-call">
<input v-model="data.password1" type="text" maxlength="32" placeholder="确认密码" password="true" />
</view>
</view>
<view class="dlbutton" hover-class="dlbutton-hover" @tap="signup(data)">
<text>登录</text>
</view>
</view>
</template>
<script setup>
import {
reactive,
watch
} from "vue"
import {
signup,
user
} from "../service";
const data = reactive({
username: "",
password: "",
password1: ''
})
watch(user, (ov, nv) => {
console.log({ ov, nv })
if (nv?._id) {
uni.reLaunch({
url: '/pages/index/index'
});
}
}, {
imediate: true
})
</script>
<style scoped="" lang="scss">
.content {
display: flex;
flex-direction: column;
justify-content: center;
}
.input-list {
padding: 50upx 70upx;
}
.list-call {
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
height: 100upx;
color: #333333;
border-bottom: 1upx solid rgba(230, 230, 230, 1);
input {
width: 100%;
}
}
.dlbutton {
color: #ffffff;
font-size: 34upx;
width: 470upx;
height: 100upx;
background: linear-gradient(
-90deg,
rgba(63, 205, 235, 1),
rgba(188, 226, 158, 1)
);
box-shadow: 0upx 0upx 13upx 0upx rgba(164, 217, 228, 0.2);
border-radius: 50upx;
line-height: 100upx;
text-align: center;
margin-left: auto;
margin-right: auto;
margin-top: 100upx;
}
.dlbutton-hover {
background: linear-gradient(
-90deg,
rgba(63, 205, 235, 0.9),
rgba(188, 226, 158, 0.9)
);
}
.addition {
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
font-size: 30upx;
margin-top: 80upx;
color: #ffa800;
text-align: center;
height: 40upx;
line-height: 40upx;
}
</style>
{
"pages": [ //pages数组中第一项表示应用启动页,参考:https://uniapp.dcloud.io/collocation/pages
{
"path": "pages/index/index",
"style": {
"navigationBarTitleText": "DDP Demo"
}
}
,{
"path" : "modules/user/login/login",
"style" :
{
"navigationBarTitleText": "登录",
"enablePullDownRefresh": false
}
}
,{
"path" : "modules/user/signup/signup",
"style" :
{
"navigationBarTitleText": "",
"enablePullDownRefresh": false
}
}
],
"globalStyle": {
"navigationBarTextStyle": "black",
"navigationBarTitleText": "uni-app",
"navigationBarBackgroundColor": "#F8F8F8",
"backgroundColor": "#F8F8F8"
}
}
<template>
<view class="content">
<image class="logo" src="/static/logo.png"></image>
<view class="text-area" v-for="item in list" :key="item._id">
{{item.name}}
</view>
</view>
</template>
<script setup>
import { ddp } from "../../modules/core/ddp";
import { reactive } from "vue";
const list = reactive([])
ddp.map('rooms',list)
</script>
<style>
.content {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.logo {
height: 200rpx;
width: 200rpx;
margin-top: 200rpx;
margin-left: auto;
margin-right: auto;
margin-bottom: 50rpx;
}
.text-area {
display: flex;
justify-content: center;
}
.title {
font-size: 36rpx;
color: #8f8f94;
}
</style>
/**
* 这里是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:12px;
$uni-font-size-base:14px;
$uni-font-size-lg:16;
/* 图片尺寸 */
$uni-img-size-sm:20px;
$uni-img-size-base:26px;
$uni-img-size-lg:40px;
/* Border Radius */
$uni-border-radius-sm: 2px;
$uni-border-radius-base: 3px;
$uni-border-radius-lg: 6px;
$uni-border-radius-circle: 50%;
/* 水平间距 */
$uni-spacing-row-sm: 5px;
$uni-spacing-row-base: 10px;
$uni-spacing-row-lg: 15px;
/* 垂直间距 */
$uni-spacing-col-sm: 4px;
$uni-spacing-col-base: 8px;
$uni-spacing-col-lg: 12px;
/* 透明度 */
$uni-opacity-disabled: 0.3; // 组件禁用态的透明度
/* 文章场景相关 */
$uni-color-title: #2C405A; // 文章标题颜色
$uni-font-size-title:20px;
$uni-color-subtitle: #555555; // 二级标题颜色
$uni-font-size-subtitle:26px;
$uni-color-paragraph: #3F536E; // 文章段落颜色
$uni-font-size-paragraph:15px;
## 1.0.0(2021-10-01)
1. 初始化,ddp所需依赖
此差异已折叠。
export * from "./hj-core.es.js"
\ No newline at end of file
{
"id": "hj-core",
"displayName": "hj-core",
"version": "1.0.0",
"description": "hj-x 系列公共依赖的存放点",
"keywords": [
"hj-core",
"hj-*"
],
"repository": "",
"engines": {
"HBuilderX": "^3.1.0"
},
"dcloudext": {
"category": [
"JS SDK",
"通用 SDK"
],
"sale": {
"regular": {
"price": "0.00"
},
"sourcecode": {
"price": "0.00"
}
},
"contact": {
"qq": ""
},
"declaration": {
"ads": "无",
"data": "无",
"permissions": "无"
},
"npmurl": ""
},
"uni_modules": {
"dependencies": [],
"encrypt": [],
"platforms": {
"cloud": {
"tcb": "y",
"aliyun": "y"
},
"client": {
"Vue": {
"vue2": "y",
"vue3": "y"
},
"App": {
"app-vue": "y",
"app-nvue": "y"
},
"H5-mobile": {
"Safari": "y",
"Android Browser": "y",
"微信浏览器(Android)": "y",
"QQ浏览器(Android)": "y"
},
"H5-pc": {
"Chrome": "y",
"IE": "y",
"Edge": "y",
"Firefox": "y",
"Safari": "y"
},
"小程序": {
"微信": "y",
"阿里": "y",
"百度": "y",
"字节跳动": "y",
"QQ": "y"
},
"快应用": {
"华为": "y",
"联盟": "y"
}
}
}
},
"main": "./js_sdk/hj-core.umd.js",
"module": "./js_sdk/hj-core.es.js",
"exports": {
".": {
"import": "./js_sdk/hj-core.es.js",
"require": "./js_sdk/hj-core.umd.js"
}
}
}
\ No newline at end of file
# hj-utils
\ No newline at end of file
## 1.0.1(2021-10-03)
1. 新增利用use 注册插件
## 1.0.0(2021-10-02)
[1.0.0]
2021.10.2 再不发一个,我觉得自己会一直拖下去了,凑合能用,后面补文档和优化,我在想要不要在某个博客写 - demo最近这两天发
###已完成
>1. 使用websocket协议连接ddp服务器 [例如meteor]
>2. 订阅数据
>3. 调用方法
>4. 映射数据至vue响应式数组数据源
>5. 简化的Mininongo,使用它的selector进行的重写
###暂不支持
>1. 账号服务 - 在研究中,计划绑定在实例
>2. Autorun - 不计划写,vue有足够的响应式支持了,当订阅参数变化时,取消上次订阅进行重新订阅即可
>3. 本地数据库操作直接反应到远程数据库 - 感觉有点风险,还是建议调用方法,改完后影响的数据如果有订阅,结果会挺快推到前台的。
>4. 文件传输,呃,这个不打算适配了,大文件建议直接upload吧
###想做但不好做的
>1. 本地数据缓存: 因为取消订阅会触发删除数据操作,本地缓存就有点难,因为无法区分删除操作是取消订阅引起的还是真正的删除数据操作 - 但因为发布的数据往往只是文档的一部分,所以想追踪其实有点不太好弄
###避坑指南
>1. 发布源的名称并不是数据集的名称
>2. 方法和发布可以取名为 a_b_c,或者 'a.b.c' 或者 'a/b/c' 而不是函数命名的驼峰形式
此差异已折叠。
export interface IDDPClientOption {
socketConstructor?: Socket;
tlsOpts?: any;
autoReconnect?: boolean;
autoReconnectTimer?: number;
maintainCollections?: boolean;
url?: string;
ddpVersion?: string;
storageKey?: string;
}
export interface Socket {
send: (msg: string | ArrayBuffer) => any;
onclose: (ev: { code?: number; reason?: string }) => void;
onopen: () => void;
onmessage: (msg: { data: any }) => void;
close: () => void;
onerror: (err: any) => void;
}
export interface SocketConstructor {
new (url: string, protos?: string | string[], confs?: any): Socket;
}
export interface IDDPOption {
socketConstructor?: SocketConstructor;
tlsOpts?: any;
autoConnect?: boolean;
autoReconnect?: boolean;
autoReconnectTimer?: number;
maintainCollections?: boolean;
url?: string;
ddpVersion?: string;
db: IMongo;
}
export interface IDDPClient {
connection: IDDPConnection;
db: IMongo;
use(plugin:any,options?:any):any
call(name: string, arg?: any, ready?: Function, updated?: Function): void;
subscribe(name: string, arg?: any, ready?: Function): string;
unsubscribe(id: string): void;
destroy(): void;
map<T extends IDocument = any>(
collectionName: string | ICollection<T>,
holder: any[],
selector: any,
options?: { id?: string; transform: (doc: any) => any }
): {
stop: () => void;
};
}
export interface IMongo {
collection<T extends IDocument = any>(name: string): ICollection<T>;
name: string;
}
export interface IDocument {
_id: string;
[key: string]: any;
}
export interface ICollection<T extends IDocument = any> {
find(selector?: any, opt?: { transform: (doc: any) => any }): Partial<T>[];
findOne(selector?: any, opt?: { transform: (doc: any) => any }): Partial<T>;
observe(handler:ICollectionObserverHandler):void
}
export interface ICollectionObserverHandler<T = any> {
added: (id: string, data: T) => any;
changed?: (id: string, fields: Partial<T>, removeFields: string[]) => any;
removed?: (id: string) => any;
error?: (err: any) => any;
addedBefore?: (id: string, fields: any, before: any) => any;
movedBefore?: (id: string, before: any) => any;
}
export interface IEventEmitter {
maxListeners: number;
emit(type, ...args): boolean;
on(type, listener, prepend?: boolean);
once(type, listener, prepend?: boolean): IEventEmitter;
off(type, listener): IEventEmitter;
listenerCount(type): number;
eventNames(): string[];
}
export enum IDDPConnectionState {
CLOSED = 0,
CONNECTING = 1,
CONNECTED = 2,
READY = 3,
FAIL = 4,
CLOSING = 5,
RECONNECTING = 6,
}
export interface IDDPConnection extends IEventEmitter {
tlsOpts: any;
autoReconnect: boolean;
autoReconnectTimer: number;
url: string;
socketConstructor: SocketConstructor;
ddpVersion: string;
db: IMongo;
socket: Socket;
session: any;
state: IDDPConnectionState;
isSocketBusy: boolean;
connect(url?: string, protos?: any, data?: any): void;
close(): void;
call(name, params, callback?: any, updatedCallback?: any): void;
subscribe(name: string, params: any, callback?: any): string;
unsubscribe(id: string): void;
}
export * from "./hj-ddp.es.js"
\ No newline at end of file
{
"id": "hj-ddp",
"displayName": "hj-ddp",
"version": "1.0.1",
"description": "一个uniapp的DDP客户端实现,助你成为全栈工程师,哈哈~",
"keywords": [
"hj-ddp",
"DDP",
"Meteor",
"数据同步",
"响应式数据"
],
"repository": "",
"engines": {
"HBuilderX": "^3.1.0"
},
"dcloudext": {
"category": [
"JS SDK",
"通用 SDK"
],
"sale": {
"regular": {
"price": "0.00"
},
"sourcecode": {
"price": "0.00"
}
},
"contact": {
"qq": ""
},
"declaration": {
"ads": "无",
"data": "无",
"permissions": "暂无"
},
"npmurl": ""
},
"uni_modules": {
"dependencies": [
"hj-core"
],
"encrypt": [],
"platforms": {
"cloud": {
"tcb": "y",
"aliyun": "y"
},
"client": {
"Vue": {
"vue2": "y",
"vue3": "y"
},
"App": {
"app-vue": "y",
"app-nvue": "u"
},
"H5-mobile": {
"Safari": "y",
"Android Browser": "y",
"微信浏览器(Android)": "y",
"QQ浏览器(Android)": "y"
},
"H5-pc": {
"Chrome": "y",
"IE": "y",
"Edge": "y",
"Firefox": "y",
"Safari": "y"
},
"小程序": {
"微信": "y",
"阿里": "y",
"百度": "u",
"字节跳动": "u",
"QQ": "u"
},
"快应用": {
"华为": "u",
"联盟": "u"
}
}
}
}
}
\ No newline at end of file
# hj-ddp
## 一个Uniapp的DDP实现,助你成为全栈工程师
>DDP是什么
>>DDP是一个 分布式数据协议,主要是为了解决不同终端数据同步的问题,使用简单的协议来实现多个终端的数据一致性。由Meteor提出和维护。Vue创造者当时就是从Meteor离职然后全职维护vue的。
>它有什么好处
>>DDP是一个异步的数据推送,而且做到订阅了的数据会直接推送,所以:
>>>小明改了个自己的昵称、另一个人会很快看到改动;
>>>订单状态变了,所在的列表会自动刷新,而不需要进行一次请求
>它的原理和不足、使用建议
>>【推荐使用Meteor作为服务端,参考demo】 基于Mongodb以及订阅发布模式,在ddp服务器端自定义个发布源:可以参考MongoDB的watch API或者冒出来的大量livequery服务(它们都收费的),如果改动的数据集满足定义的选择器(例如{age:{$gt:30}这种选择30岁以上的},就会把这个改动推送给所有的订阅者
>>基于Websocket实现双工通讯,因为要维护连接,所以可能更耗资源?但有点不能认同,因为websocket建立起来后就不需要多次握手,以及重复的身份验证
>>>1.高频更新的数据很合适!
>>>2.大文件(100k)和长音频等媒体文件,别用!
>>>3.静态资源文件,别用!
## 使用指南 -【typescript】
### 1. 建立连接 - 确保安装了hj-core
```
import { init, IDDPClient } from "../../uni_modules/hj-ddp/js_sdk"
// ts
export const ddp: IDDPClient = init("ws://localhost:3002");
// js
export const ddp = init("ws://localhost:3002");
```
####1. init : 参数 string | undefined | IDDPClientOption
```
interface IDDPClientOption {
socketConstructor?: Socket;
tlsOpts?: any;
autoReconnect?: boolean;
autoReconnectTimer?: number;
url?: string;
ddpVersion?: string;
storageKey?: string;
}
```
>>说明: 缺省值为ws://localhost:3000 - meteor默认地址,Mongodb为meteor端口+1或者自己传入 ;同一个ddp服务地址只会建立一个客户端
>>>IDDPClientOption.socketConstructor: 自定义的socket构造函数,已经封装了个uniapp的,所以不需要传入,自己可以封装个http的只要实现Socket的方法即可
>>>IDDPClientOption.tlsOpts: 暂时没用到,后面可能作为连接参数
>>>IDDPClientOption.autoReconnect: 自动重连-默认true
>>>IDDPClientOption.autoReconnectTimer: 自动重连间隔-默认10000ms = 10s
>>>IDDPClientOption.url: ddp服务地址,同字符串方式的
>>>IDDPClientOption.ddpVersion: ddp协议版本,默认 1,当前版本,协议挺稳定,基本没改过
>>>IDDPClientOption.storageKey: 可以传个表示client数据缓存的key,但因为这个不好做【见changelog说明】所以意义不大
>>返回:IDDPClient
### 2. IDDPClient说明 - 简单的看接口定义
```
interface IDDPClient {
db: IMongo;
call(name: string, arg?: any, ready?: Function, updated?: Function): void;
subscribe(name: string, arg?: any, ready?: Function): string;
unsubscribe(id: string): void;
destroy(): void;
map<T extends IDocument = any>(
collection: string | ICollection<T>,
holder: any[],
selector: any,
options?: { id?: string; transform: (doc: any) => any }
): {
stop: () => void;
};
}
```
> 1. call: 调用远程定义的方法,只支持一个参数传送给远端*,ready回调会传入远程方法的返回值,updated会在call方法修改了文档+订阅了这个文档,且本地更新完后调用 - (例如你新增一个订单,成功后会返回订单id,但不一定已经入库了,updated会晚一点执行,但是如果一个方法修改了文档然后做了些其它耗时的操作updated会早于ready)
> 2. subscribe: 订阅一个发布源,可以传入一个参数,ready方法在订阅源的所有文档都在本地后调用
> 3. map : 第一个参数可以是数据集的名称或者已经创建的数据集,第二个参数是一个响应式数组例如传入一个reactive([])-vue3构建的引用或者this.list-vue2,然后第三个参数是一个选择器或者过滤器: 支持 string | Function | MongoSelector- 后面更新再详细说,用来过于文档,第四个传输让你可以传入一个独一无二的id避免重复订阅或者一个transform函数转化每个文档为新数据
> 4. unsubscribe: 将subscribe返回的id传入即可取消订阅,订阅相关的数据会被清除~
> 5. 属性 db- 本链接绑定的数据库对象Mongo
### 3. Mongo/Collection 说明
>除了使用ddp.map方法映射数据到一个响应式数组,你还可以 直接观察某个数据集的数据
```
// 订阅数据
const id = ddp.subscribe("todos",{userId:'233'})
// 创建数据集 - 单例模式,也就是不会重复创建同名的数据集
const Todos = ddp.db.collection("todos");
// 查找所有数据:
const todos = Todos.find()
// 查找一条数据:
const todos = Todos.find({due:{$gt:1}})
// 映射数据:
const todos = reactive([])
const {stop} = ddp.map(Todos, list, {
}, {
transform: doc => ({
...doc,
time: Date.now()
})
})
```
接口说明:
```
interface IMongo {
collection<T extends IDocument = any>(name: string): ICollection<T>;
name: string;
}
ICollection<T extends IDocument = any> {
find(): Partial<T>[];
findOne(): Partial<T>;
observe(handler:ICollectionObserverHandler):void
}
ICollectionObserverHandler<T = any> {
added: (id: string, data: T) => any;
changed?: (id: string, fields: Partial<T>, removeFields: string[]) => any;
removed?: (id: string) => any;
error?: (err: any) => any;
addedBefore?: (id: string, fields: any, before: any) => any;
movedBefore?: (id: string, before: any) => any;
}
```
declare interface IDDPClient {
loginWithPassword(u: string, password: string): Promise<any>;
user: {
state: 0 | 1 | 2; // offline logined loging
info: any;
onChange(cb: Function): void;
};
logout(): Promise<any>;
createAccount(u: string, password: string): Promise<any>;
}
import { UniDB } from "../../hj-core/js_sdk";
export function hjMeteorAccount(client, opts) {
const users = client.db.collection("users");
const cacher = opts?.cacher || new UniDB("hj-meteor-user");
const od = cacher.get("login");
const ucs = [];
client.user = {
state: 0,
info: null,
onChange(cb) {
ucs.push(cb);
},
};
function notify() {
ucs.forEach((cb) => {
if (typeof cb === "function") cb(client.user.info);
});
}
if (od) {
if (new Date(od.tokenExpires).getTime() > Date.now()) {
// relogin
client.user.state = 2;
client.call("login", { resume: od.token }, (err, result) => {
if (err) {
client.user.state = 0;
cacher.set("login", "");
client.user.info = null;
return;
} else {
client.user.state = 1;
cacher.set("login", result);
client.user.info = users.findOne(result.id);
}
notify();
});
} else cacher.set("login", "");
}
client.loginWithPassword = (selector, password) =>
new Promise((resolve, reject) => {
const request = {};
client.user.state = 2;
if (typeof selector === "string") {
if (selector.indexOf("@") === -1) request.username = selector;
else request.email = selector;
}
request.password = password;
client.call(
"login",
request,
(err, result) => {
if (err) {
client.user.state = 0;
cacher.set("login", "");
client.user.info = null;
return reject(err);
}
client.user.state = 1;
cacher.set("login", result);
client.user.info = users.findOne(result.id);
notify();
resolve(client.user.info);
}
);
});
client.logout = () =>
new Promise((resolve) =>
client.call("logout", (err, res) => {
let os = client.user.state;
client.user.state = 0;
cacher.set("login", "");
client.user.info = null;
if (os) notify();
resolve(true);
})
);
client.createAccount = (selector, password) =>
new Promise((resolve, reject) => {
const request = {};
client.user.state = 2;
if (typeof selector === "string") {
if (selector.indexOf("@") === -1) request.username = selector;
else request.email = selector;
}
request.password = password;
request.profile = {
name: "用户-" + selector.slice(0, 10)
}
client.call(
"createUser",
request,
(err, result) => {
if (err) {
client.user.state = 0;
cacher.set("login", "");
client.user.info = null;
return reject(err);
}
client.user.state = 1;
cacher.set("login", result);
client.user.info = users.findOne(result.id);
notify();
resolve(client.user.info);
}
);
});
}
{
"id": "hj-meteor-account",
"displayName": "hj-meteor-account",
"version": "1.0.0",
"description": "hj-meteor-account",
"keywords": [
"hj-meteor-account"
],
"repository": "",
"engines": {
"HBuilderX": "^3.1.0"
},
"dcloudext": {
"category": [
"JS SDK",
"JS SDK"
],
"sale": {
"regular": {
"price": "0.00"
},
"sourcecode": {
"price": "0.00"
}
},
"contact": {
"qq": ""
},
"declaration": {
"ads": "",
"data": "",
"permissions": ""
},
"npmurl": ""
},
"uni_modules": {
"dependencies": [],
"encrypt": [],
"platforms": {
"cloud": {
"tcb": "u",
"aliyun": "u"
},
"client": {
"Vue": {
"vue2": "u",
"vue3": "u"
},
"App": {
"app-vue": "u",
"app-nvue": "u"
},
"H5-mobile": {
"Safari": "u",
"Android Browser": "u",
"微信浏览器(Android)": "u",
"QQ浏览器(Android)": "u"
},
"H5-pc": {
"Chrome": "u",
"IE": "u",
"Edge": "u",
"Firefox": "u",
"Safari": "u"
},
"小程序": {
"微信": "u",
"阿里": "u",
"百度": "u",
"字节跳动": "u",
"QQ": "u"
},
"快应用": {
"华为": "u",
"联盟": "u"
}
}
}
}
}
\ No newline at end of file
# hj-meteor-account
\ No newline at end of file
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册