ES6+开发电商账号体系SDK
# ES6+开发电商账号体系SDK
- 电商账号体系 SDK
- 使用原生 ES6 ,不依赖框架,使用模块、请求和事件绑定,实现登录注册、账号设置、密码找回模块。
- 借助 fetchmock 插件,模拟 http 请求和 mock 数据,并使用 fetch 配合 async / await 实现异步操作。
- 项目遵守设计原则,代码具有通用性、兼容性、核心能力的封装性和外部配置的灵活性。
[TOC]
# 一、环境准备
# 1.1 html-bundler(脚手架工具)
地址:https://github.com/be-fe/html-bundler
- 解决babel和webpack配置比较繁琐的问题。
- 性能与日志优化,html/css/图片等的处理。
- Dev Server和环境配置。
// 使用html-bundler初始化项目
$ npm i html-bundler -g
// 这里 -w 表示使用-webpack
$ hb create account-system-sdk -w
$ cd account-system-sdk && npm install && hb dev
1
2
3
4
5
2
3
4
5
# 1.2 安装必要的polyfill
// 兼容ES5的API(硬垫片)
$ npm i es5-shim --save-dev
// 兼容ES6新的API(软垫片)
$ npm i babel-polyfill --save-dev
1
2
3
4
2
3
4
// webpack.dll.js
const vendors = [
'es5-shim',
'babel-polyfill'
];
1
2
3
4
5
2
3
4
5
PS:babel-runtime使用与性能优化 (opens new window)
使用babel-plugin-transform-runtim取代babel-polyfill,可以避免包太大,且可以自动分析安装需要兼容API对应的polyfill,而不是安装全部polyfill。
babel-preset-env取代babel-preset-2015。babel-preset-env将基于你的实际浏览器及运行环境,自动的确定babel插件及polyfill,编译ES2015及此版本以上的语言。
由于dll发生了变化,要重新生成。
$ npm run dll
1
# 1.3 加入gulp-file-include
实现模板代码复用,比如@@include('./templates/header.html',{})
$ npm i gulp-file-include --save-dev
1
// html-bundler.config.js
var fileinclude = require('gulp-file-include');
var destMod = {
custom: {
html: [{func: fileinclude, opts: { prefix: '@@', basepath: '@file'}}]
}
}
module.exports = {
devMod: {
custom: {
html: [{func: fileinclude, opts: { prefix: '@@', basepath: '@file'}}]
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
引入新的插件后,要重新启动。
$ hb dev
1
// src/html/templates/header.html
<div class="header">header</div>
// src/templates/footer.html
<div class="footer">footer</div>
// src/html/index.html
<body>
@@include('./templates/header.html',{})
@@include('./templates/footer.html',{})
</body>
1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
# 二、需求分析和架构设计
# 2.1 需求分析
# 2.1.1 产品要求
- 包含登录/注册/找回密码/信息设置与修改
- 支持PC和移动端,各个子网站需要自己的样式
- 功能逻辑必须统一和同步
# 2.1.2 技术应对
- 通过JS SDK的方式,由一个团队统一开发维护,保证功能的统一以及修改的同步。
- 支持PC和移动端,因此包体积要小,要分包,不能有依赖。
- JS SDK要包含全部业务逻辑,对外不开放修改;但不包含具体样式,由下游业务方进行自定义。
# 2.1.3 前端技术指标
- 浏览器兼容到IE8。
- 支持PC和移动端,大小不能超过30kb。
- 支持多种引用方式:直接引用,commonJS,AMD。
# 2.2 前端架构设计
原则:自顶向下,自外而内。
- 对外的API接口设计。
- 模块的划分与关联。
- 模块的具体实现与一般性套路。
# 2.2.1 前端对外API设计
简单易用,封装性,灵活性。
- 暴露什么样的接口?(类or普通函数or对象)
- 有哪些配置项?
- 默认值是什么?
# 2.2.1.1 如何编写公共模块
- 对外暴露函数
- 单一功能,且无内部状态。
var login = pass.login({
container: ducument.getElementById('login-container'),
autocomplete: false,
success: function() {
location.replace('profile.html')
}
})
1
2
3
4
5
6
7
2
3
4
5
6
7
- 对外暴露对象
- 无关联的功能合集,且无内部状态。
- 对外暴露class(构造函数)
- 互相关联的功能集合或存在内部状态的动能。
# 2.2.2 模块的划分与关联
# 2.2.3 模块的具体实现与一般套路
# 2.2.3.1 业务模块编写
- init:初始化,用于接收参数和设置初始值。
- render:渲染。
- event:事件绑定。
// src/js/login/init.js
import render from './render.js'
import event from './event.js'
window.login = (opts) => {
const container = opts.container;
render(container);
event();
}
1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
// src/js/login/render.js
export default (container) => {
const tpl = `<form>
<input name="uname" type="text">
<input name="password" type="password">
<input id="submit" value="login" type="submit">
</form>`;
container.innerHTML = tpl;
}
1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
// src/js/login/event.js
export default () => {
const btn = document.getElementById('submit');
btn.onclick = () => {
alert('1111');
};
}
1
2
3
4
5
6
7
2
3
4
5
6
7
// src/html/index.html
<div id="container"></div>
<script type="text/javascript" src="../js/login/init.js"></script>
<script>
login({
container: document.getElementById('container')
});
</script>
1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
# 2.2.3.2 公共模块编写
// src/js/common/formCheck.js
export default (form) => {
return () => {
alert(form.id);
return [];
}
}
1
2
3
4
5
6
7
2
3
4
5
6
7
// src/js/login/event.js
import formCheck from '../common/formCheck.js'
export default () => {
const btn = document.getElementById('submit');
const input = document.getElementById('input');
const check = formCheck(document.getElementById('form'));
btn.onclick = () => {
check();
};
input.oninput = () => {
check();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
// src/js/login/render.js
export default (container) => {
const tpl = `<form id="form">
<input id="input" name="uname" type="text">
<input name="password" type="password">
<input id="submit" value="login" type="submit">
</form>`;
container.innerHTML = tpl;
}
1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
# 三、业务开发
# 3.1 登录模块
# 3.1.1 骨架及渲染
// src/js/common/polyfill.js
import 'es5-shim';
import "babel-polyfill";
1
2
3
2
3
// src/js/common/utils.js
// 实现简写
const getId = (id) => {
const dom = ducument.getElementById(id);
// 给id添加随机数,避免与业务方的id重名
dom && dom.setAttribute('id', dom.id + '-' + Math.floor(Math.random * 100000));
return dom;
}
export { getId as $ }
1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
// src/js/login/init.js
import '../common/polyfill';
import render from './render';
import bindEvent from './event';
window.login = (opts = {}) => {
var defaultOpts = {
loginBtnText: '登 录',
accountPlaceHolder: '手机号/邮箱/账号',
accountLabel: '',
passwordPlaceHolder: '请填写密码',
passwordLabel: '',
verifyPlaceHolder: '验证码',
accountMax: '30',
passwordMax: '30',
showRemember: true,
autocomplete: false,
};
// 将自定义参数覆盖默认参数
var options = Object.assign(defaultOpts, opts)
render(options);
// bindEvent(options);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// src/js/login/render.js
const tpl = (opts = {}) => {
// 用来兼容chrome下的自动填写
const autocompleteTpl =
`<div id="no-autocomplete">
<input type="text">
<input type="password">
</div>`;
const showRemember = opts.showRemember ? 'block' : 'none';
const autocompleteAdapter = opts.autocomplete ? '' : autocompleteTpl;
// 不能兼容chrome
const autocompleteValue = opts.autocomplete ? 'on' : 'off';
const tpl = `
<div id="login-wrapper">
<p id="login-error" class="login-error"></p>
<form id="login-form" onsubmit="return false">
${ autocompleteAdapter }
<label class="login-accound-wrapper">
<span class="account-label">${ opts.accountLabel }</span>
<input id="login-account" valid="present" name="account" type="text" placeholder="${ opts.accountPlaceHolder }" maxlength="${opts.accountMax}" autocomplete="${ opts.autocompleteValue }">
<span id="clear-account" class="del"></span>
</label>
<label class="login-password-wrapper">
<span class="password-label">${ opts.passwordLabel }</span>
<input id="login-password" valid="present" name="password" type="password" placeholder="${ opts.passwordPlaceHolder }" maxlength="${opts.passwordMax}" autocomplete="${ autocompleteValue }">
</label>
<label style="display: none" class="login-verify-wrapper">
<span class="verify-label">验证码:</span>
<input id="login-verify" name="verifyCode" type="text" placeholder="${ opts.verifyPlaceHolder }">
<img src="/verifycode">
</label>
<label class="login-remember-wrapper" style="display: ${ showRemember }">
<span>记住密码:</span>
<input id="login-remember" name="remember" type="checkbox">
</label>
<input id="login-btn" class="login-btn" type="submit" value="${ opts.loginBtnText }">
</form>
<div class="login-extra-wrapper">
<a href="forget.html">忘记密码</a>
<a href="register-mobile.html">免费注册</a>
</div>
</div>`
return tpl;
}
export default (conf) => {
conf.container.innerHTML = tpl(conf);
let $noAutocomplete = document.getElementById('no-autocomplete');
if ($noAutocomplete) {
// 这么写避免样式被覆盖
// 不能用opacity,这样还是可以被点击,并且遮挡其他的点击
$noAutocomplete.style.visibility = 'hidden';
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
// src/html/index.html
<body id="login">
@@include('./templates/header.html', {})
<div class="back">
<div id="login-area">
<h1>密码登录</h1>
<div id="login-container"></div>
</div>
</div>
@@include('./templates/footer.html', {})
<script type="text/javascript" src="../lib/vendors.js"></script>
<script src="../js/login/init.js"></script>
<script type="text/javascript">
login({container: document.getElementById('login-container'),
autocomplete: false,
success: function() {
location.replace('profile.html');
}});
</script>
</body>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 3.1.2 表单验证事件请求
// src/js/login/events.js
import { $ } from '../common/utils.js'
export default (opts = {}) => {
const $loginForm = $('login-form');
const $loginBtn = $('#login-btn');
const $remember = $('#login-remember');
const $clearAccount = $('#clear-account');
const $clearPassword = $('#clear-password');
const $account = $('#login-account');
const $password = $('#login-password');
const $error = $('#login-error');
}
1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
# 3.1.2.1 fetchmock模拟登录数据
// 模拟使用fetch创建的 http请求
$ npm i fetch-mock --save-dev
1
2
2
参考文档:API-docs|fetchmock (opens new window)
示例用法:
fetchMock.mock('http://example.com', 200); const res = await fetch('http://example.com'); assert(res.ok); fetchMock.restore();
1
2
3
4
// src/js/common/mock.js
import FetchMock from 'fetch-mock';
// 配置需要mock的路由
FetchMock.mock('/login', (url, opts) => {
const params = opts.params;
if (params.account === '15876501962') {
if (params.password === '123456') {
return {code: 200, message: 'success'};
}
else {
return {code: 401, message: '密码错误'};
}
}
else {
return {code: 400, message: '用户名错误'};
}
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// src/js/common/polyfill.js
import './mock'
1
2
2
//src/js/login/event.js
import { fetchPost } from '../common/fetch';
let data = await fetchPost('/login', {
account: $account.value,
password: $password.value,
remember: remember
});
if (data.code === 200) {
alert('登录成功');
opts.success && opts.success();
}
else {
$error.innerHTML = data.message;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 3.1.2.2 表单校验
// src/js/common/form-check.js
const formatText = (key) => {
return '您填写的' + key + '格式不正确'
};
// 校验规则
const rules = {
email: (v) => {
if (!v.match(/^([a-zA-Z0-9_\.\-])+\@(([a-zA-Z0-9\-])+\.)+([a-zA-Z0-9]{2,4})+$/)) {
return {
type: 'email',
message: formatText('邮箱')
}
}
},
mobile: (v) => {
if (!v.match(/^1(3|4|5|7|8)\d{9}$/)) {
return {
type: 'mobile',
message: formatText('手机号')
}
}
},
IDcard: (v) => {
return {
type: 'IDcard',
message: formatText('身份证号')
}
},
present: (v) => {
if (!v.trim()) {
return {
type: 'present',
message: '必填'
}
}
}
}
class FormCheck {
constructor (opts) {
this.opts = opts;
}
check (form) {
// 容错处理
const elements = this.opts.form.elements || document.getElementById(this.opts.form).elements;
// 存放错误处理消息
let checkResults = [];
// 对设有valid属性的标签的值进行校验
Array.from(elements).filter( (item) => {
return item.getAttribute('valid');
}).map((item) => {
const valids = item.getAttribute('valid').split(', ');
const value = item.value;
let errorArr = [];
valids.forEach((valid) => {
if (rules[valid]) {
let result = rules[valid](value);
result && errorArr.push(result);
}
})
if (errorArr.length) {
checkResults.push({
dom: item,
errorArr: errorArr,
name: item.name,
message: errorArr[0].message,
type: errorArr[0].type
});
}
});
return checkResults
}
}
export default FormCheck
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
// src/js/common/fetch.js
/*
* Fetch 请求默认是不带 cookie 的,需要设置 fetch(url, {credentials: 'include'})
服务器返回 400,500 错误码时并不会 reject,只有网络错误这些导致请求不能完成时,fetch 才会被 reject。
*/
const fetchPost = (url, params) => {
return fetch(url, {
method: 'POST',
headers: {
"Content-Type": "application/x-www-form-urlencoded"
},
credentials: 'include',
params: params
}).then((res) => {
if (!res.ok) {
throw Error(res.statusText);
}
return res.json();
});
};
const fetchJson = (url, params) => {
return fetch(url, {
method: 'GET',
headers: {},
credentials: 'include',
params: params
}).then((res) => {
if (!res.ok) {
throw Error(res.statusText);
}
return res.json();
});
};
export {fetchPost, fetchJson};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
// src/js/login/event.js
import FormCheck from '../common/form-check';
const formCheck = new FormCheck({
form: document.getElementById('login-form')
});
$loginBtn.onclick = async () => {
// 重新提交的时候重置错误提示框
$error.innerHTML = '';
const checkResults = formCheck.check();
if (!checkResults.length) {
// 点击后设置为不可点状态
$loginBtn.setAttribute('disabled', 'disabled');
// 判断是否记住密码
let remember = '0';
if ($remember.getAttribute('checked')) {
remember = '1'
}
// 这一步操作完成后才会往下进行
let data = await fetchPost('/login', {
account: $account.value,
password: $password.value,
remember: remember
});
if (data.code === 200) {
alert('登录成功');
opts.success && opts.success();
}
else {
$error.innerHTML = data.message;
}
// 请求完成后按钮设置为可点击状态
$loginBtn.removeAttribute('disabled');
}
else {
const name = checkResults[0].name;
const type = checkResults[0].type;
if (type === 'present') {
if (name === 'account') {
$error.innerHTML = '请填写您的用户名';
}
if (name === 'password') {
$error.innerHTML = '请填写您的密码';
}
}
}
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
# 3.2 注册模块
# 3.2.1 手机号注册
# 3.2.1.1 活人滑块验证
// src/js/common/slider.js
/*
* @file 滑块验证插件
* @params Dom opts.container 渲染父容器
* @params String opts.unsuccessTip 未验证成功时的提示语
* @params String opts.successTip 验证成功时的提示语
*/
import utils from '../common/utils';
const { domSelector: $ } = utils;
// symbol可用于私有方法
const render = Symbol('render');
const bindEvent = Symbol('bindEvent');
const style =
`<style>
.register-verify-wrapper {
height: 36px;
font-size: 14px;
line-height: 36px;
}
.vs-wrapper {
position: relative;
width: 300px;
height: 100%;
}
.vs-moved-bg {
background: #7ac23c;
width: 0;
position: absolute;
z-index: 999;
height: 100%;
}
.vs-text {
position: absolute;
width: 100%;
top: 0;
z-index: 1000;
text-align: center;
color: #9c9c9c;
font-size: 12px;
}
.vs-move-btn {
height: 100%;
width: 30px;
background: #ccc;
position: absolute;
left: 0;
z-index: 1001;
}
</style>`
class Slider {
constructor(opts) {
this.opts = opts;
if (!opts.container) {
throw '请填写container配置';
}
else {
this[render](opts);
this[bindEvent](opts);
}
}
[render](opts) {
// 未验证成功时的提示语
const unsuccessTip = opts.unsuccessTip || '请按住滑块,拖动到最右边';
/*
* vs = verify-slider
*/
const tpl = style + `
<div id="vs-wrapper" class="vs-wrapper">
<div id="vs-moved-bg" class="vs-moved-bg"></div>
<span id="vs-move-btn" class="vs-move-btn"></span>
<span id="vs-text" class="vs-text" ondrag="return false;">${ unsuccessTip }</span>
</div>
`
opts.container.innerHTML = tpl;
}
[bindEvent](opts) {
const $btn = $('#vs-move-btn');
const $moved = $('#vs-moved-bg');
const $wrapper = $('#vs-wrapper');
const $text = $('#vs-text');
const reset = () => {
this.startX = 0;
this.start = false;
this.end = false;
$btn.style.left = '0px';
$moved.style.width = '0px';
}
$btn.onmousedown = (e) => {
this.startX = e.pageX;
this.start = true;
}
window.onmousemove = (e) => {
if (this.start && !this.end) {
let offset = e.pageX - this.startX;
let r1 = $moved.offsetLeft + parseInt(window.getComputedStyle($moved).width);
let r2 = parseInt(window.getComputedStyle($wrapper).width) - parseInt(window.getComputedStyle($btn).width);
$btn.style.left = offset + 'px';
$moved.style.width = offset + 'px';
if (r1 >= r2) {
this.end = true;
this.start = false;
$btn.style.left = r2 + 'px';
$moved.style.width = r2 + 'px';
opts.success && opts.success($wrapper, $text);
}
}
}
window.onmouseup = (e) => {
if (!this.end) {
reset();
}
}
}
reset() {
this[render](this.opts);
this[bindEvent](this.opts);
}
}
export default Slider
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
// src/js/register/mobile/event.js
import Slider from '../../common/slider.js';
const slider = new Slider({
container: document.getElementById('register-verify-wrapper'),
success: async ($wrapper, $text) => {
let data = await fetchPost('/getMobileVerifyToken', {});
if (data.code === 200) {
mobileVerifyToken = data.mobileVerifyToken;
$text.innerText = '验证成功';
}
else {
$text.innerText = '验证失败';
}
// 滑动成功后,让按钮可点击
$verifyBtn.removeAttribute('disabled');
removeClass($verifyBtn, 'disabled');
}
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// src/js/common/mock.js
// 获取验证token
FetchMock.mock('/getMobileVerifyToken', (url, opts) => {
return {code: 200, message: 'success', mobileVerifyToken: 'abc123456'};
});
FetchMock.mock('/register/getVerifyCode', (url, opts) => {
const params = opts.params;
return {code: 200, message: 'success', mobile: params.mobile };
});
FetchMock.mock('/register/mobile', (url, opts) => {
const params = opts.params;
if (params.verifyCode === '123456') {
return {code: 200, message: 'success'}
}
else {
return {code: 400, message: 'invalid verifyCode'}
}
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 3.2.1.2 手机号码验证
// src/common/form-check.js
mobile: (v) => {
if (!v.match(/^1(3|4|5|7|8)\d{9}$/)) {
return {
type: 'mobile',
message: formatText('手机号')
}
}
}
1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
# 3.2.2 支付方式绑定和整体串联
# 3.3 账号设置模块
# 3.4 密码找回模块
# 四、总结
# 4.1 SDK
SDK(Software Development Kit):软件开发工具包,对通用业务逻辑的封装。
常见的领域:统计、支付、云上传、IM等。
注意事项
对外暴露方便友好的接口。
灵活的参数配置和友好的参数默认值。
尽量做到无外部依赖。
# 4.2 账号体系开发要点
- 功能与体验设计
- 登录、注册、找回、账户设置、安全设置
- 前后端都需要友好的错误提示与限制
- 功能的严格限制,样式和文字的灵活限制
- 安全注意事项
- 防止注册机:活人认证
- 防止中间人截取和重放:https, 非对称加密、key与时间戳
- 跨域Post请求&前后端过滤:防止XSS注入
- 兜底的风控预警策略(后端)