PC端登录
登录(SAASLogin/v2/merchant)
说明
- 不需要headers中传token
- 入参加密参考公钥使用
- 输入:
| 名称 | 字段 | 类型 | 可选 | 说明 |
|---|---|---|---|---|
| 账号/手机号 | account | string | N | |
| 密码 | password | string | N |
- 返回的headers包含lj_encrypt字段时,需要数据解密
- 输出:
| 名称 | 字段 | 类型 | 可选 | 说明 |
|---|---|---|---|---|
| 用户名 | name | string | N | |
| 手机号 | mobile | string | N | |
| token | token | string | N | api请求,通过token进行登录认证 |
| 商户号 | merchantId | string | N | |
| 商户联系手机号 | merchantMobile | string | N | |
| 商户logo | merchantLogoImage | string | N | |
| 商户类型 | scaleType | number | N | |
| 角色 | role | string | N | |
| 权限列表 | permissionList | Object Array | N | |
| 协议价 | agreementPrice | string | N | |
| 是否绑定微信 | isBindWX | boolean | N | |
| 是否绑定小程序 | isBindMiniParam | boolean | N |
返回码
| 返回码 | 说明 |
|---|---|
| 0 | 登录成功 |
| 500 | 账号或密码错误 |
| 513 | 账号权限变更 |
| 518 | 账号频繁登录已被冻结,返回的时间为自动解封时间 |
| 519 | 账号频繁登录。最多连续登录5次,返回的数字为已登录次数 |
代码示例
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>用户登录示例</title>
<!-- 引入jQuery库 -->
<script src="https://cdn.bootcss.com/jquery/2.2.1/jquery.min.js"></script>
<!-- 引入RSA加密库 -->
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/jsencrypt/3.0.0/jsencrypt.min.js"></script>
<!-- 引入AES加密库 -->
<script src="https://cdn.bootcdn.net/ajax/libs/crypto-js/4.2.0/crypto-js.min.js"></script>
<style>
/* 登录容器样式 */
.login-container {
margin: 20px;
}
/* 输入项样式 */
.input-item {
margin: 10px 0;
}
/* 登录成功提示样式 */
.success-tip {
display: none;
margin: 20px;
color: green;
font-size: 16px;
}
/* 按钮禁用样式 */
.btn-disabled {
cursor: not-allowed;
opacity: 0.6;
}
</style>
</head>
<body>
<!-- 登录表单容器 -->
<div class="login-container" id="loginContainer">
<div class="input-item">
账号:<input type="text" id="accountInput" placeholder="请输入账号">
</div>
<div class="input-item">
密码:<input type="password" id="passwordInput" placeholder="请输入密码">
</div>
<div class="input-item">
<button id="loginBtn">登录</button>
</div>
</div>
<!-- 登录成功提示 -->
<div class="success-tip" id="successTip">登录成功</div>
<script>
/**
* 全局配置对象
* 存储API基础地址、密钥等配置信息
*/
const appConfig = {
apiBaseUrl: 'https://www.pointshow.net/api/', // API基础地址
publicKey: '', // RSA公钥(缓存用)
secretKey: '' // AES密钥(缓存用)
};
/**
* 状态管理对象
*/
const pageState = {
isLogining: false // 是否正在登录中
};
/**
* DOM元素缓存
*/
const domElements = {
loginBtn: $('#loginBtn'),
accountInput: $('#accountInput'),
passwordInput: $('#passwordInput'),
loginContainer: $('#loginContainer'),
successTip: $('#successTip')
};
// 页面初始化,绑定事件
$(function() {
bindLoginEvent();
});
/**
* 绑定登录按钮点击事件
*/
function bindLoginEvent() {
domElements.loginBtn.on('click', handleLogin);
}
/**
* 处理登录逻辑
*/
function handleLogin() {
// 防止重复点击
if (pageState.isLogining) {
return;
}
// 设置登录状态为true
pageState.isLogining = true;
updateLoginButtonState(true);
// 获取并验证输入数据
const loginData = getAndValidateInput();
if (!loginData) {
resetLoginState();
return;
}
// 获取公钥并进行RSA加密
getPublicKey(function(publicKey) {
if (!publicKey) {
alert('获取公钥失败,请刷新页面重试');
resetLoginState();
return;
}
// 对账号密码进行RSA加密
const encryptedData = rsaEncryptData(loginData, publicKey);
// 发起登录请求
requestLogin(encryptedData);
});
}
/**
* 获取并验证用户输入
* @returns {Object|null} 验证通过返回账号密码对象,否则返回null
*/
function getAndValidateInput() {
// 获取输入值并去除首尾空格和所有空白字符
const account = domElements.accountInput.val().trim().replace(/\s/g, '');
const password = domElements.passwordInput.val().trim().replace(/\s/g, '');
// 验证输入是否为空
if (!account) {
alert('请输入账号');
domElements.accountInput.focus();
return null;
}
if (!password) {
alert('请输入密码');
domElements.passwordInput.focus();
return null;
}
return { account, password };
}
/**
* 更新登录按钮状态
* @param {boolean} isDisabled - 是否禁用按钮
*/
function updateLoginButtonState(isDisabled) {
domElements.loginBtn.prop('disabled', isDisabled);
if (isDisabled) {
domElements.loginBtn.addClass('btn-disabled');
domElements.loginBtn.text('登录中...');
} else {
domElements.loginBtn.removeClass('btn-disabled');
domElements.loginBtn.text('登录');
}
}
/**
* 重置登录状态
*/
function resetLoginState() {
pageState.isLogining = false;
updateLoginButtonState(false);
}
/**
* 获取RSA公钥
* @param {Function} callback - 回调函数,返回公钥
*/
function getPublicKey(callback) {
// 如果已有公钥,直接返回
if (appConfig.publicKey) {
callback(appConfig.publicKey);
return;
}
// 从服务器获取公钥
ajaxRequest({
api: 'BizConfig/read/publicKey',
success: function(res) {
appConfig.publicKey = res.data.value;
callback(appConfig.publicKey);
},
error: function() {
callback(null);
}
});
}
/**
* RSA加密数据
* @param {Object} data - 要加密的数据(包含account和password)
* @param {string} publicKey - RSA公钥
* @returns {Object} 加密后的账号密码对象
*/
function rsaEncryptData(data, publicKey) {
const encrypt = new JSEncrypt();
encrypt.setPublicKey(publicKey);
return {
account: encrypt.encrypt(data.account),
password: encrypt.encrypt(data.password)
};
}
/**
* 发起登录请求
* @param {Object} encryptedData - 加密后的登录数据
*/
function requestLogin(encryptedData) {
ajaxRequest({
api: 'SAASLogin/v2/merchant',
data: encryptedData,
success: function(res) {
console.log('登录成功,数据:', res);
// 显示登录成功提示
showLoginSuccess();
resetLoginState();
},
error: function(err) {
handleLoginError(err);
resetLoginState();
}
});
}
/**
* 获取AES解密密钥
* @param {Function} callback - 回调函数,返回密钥
*/
function getSecretKey(callback) {
// 如果已有密钥,直接返回
if (appConfig.secretKey) {
callback(appConfig.secretKey);
return;
}
// 从服务器获取密钥
ajaxRequest({
api: 'BizConfig/read/apiAesSecretKey',
success: function(res) {
appConfig.secretKey = res.data.value;
callback(appConfig.secretKey);
},
error: function() {
callback(null);
}
});
}
/**
* AES解密数据
* @param {string} cipherText - 加密的字符串
* @param {string} key - AES密钥
* @returns {string} 解密后的字符串
*/
function aesDecrypt(cipherText, key) {
try {
// 解析密钥和密文
const decodedKey = CryptoJS.enc.Base64.parse(key);
const decodedCipherText = CryptoJS.enc.Base64.parse(cipherText);
// AES解密(ECB模式,Pkcs7填充)
const decrypted = CryptoJS.AES.decrypt(
{ ciphertext: decodedCipherText },
decodedKey,
{
mode: CryptoJS.mode.ECB,
padding: CryptoJS.pad.Pkcs7
}
);
// 返回UTF8格式的解密结果
return decrypted.toString(CryptoJS.enc.Utf8);
} catch (error) {
console.error('AES解密失败:', error);
alert('数据解密失败');
return '';
}
}
/**
* 处理登录错误
* @param {Object} err - 错误对象
*/
function handleLoginError(err) {
switch (err.code) {
case 519:
alert('账号或密码错误,请重新输入');
break;
case 404:
alert('请求接口不存在');
break;
case 500:
alert('服务器内部错误,请稍后重试');
break;
default:
alert('登录失败:' + (err.msg || '未知错误'));
break;
}
console.error('登录错误:', err);
}
/**
* 显示登录成功提示
*/
function showLoginSuccess() {
domElements.loginContainer.hide();
domElements.successTip.show();
}
/**
* 封装AJAX请求,可以参考解密部分
* @param {Object} options - 请求配置
* @param {string} options.api - 接口地址(相对路径)
* @param {Object} [options.data] - 请求数据
* @param {Function} [options.success] - 成功回调
* @param {Function} [options.error] - 失败回调
*/
function ajaxRequest(options) {
// 构造完整的请求URL
const requestUrl = appConfig.apiBaseUrl + options.api;
$.ajax({
type: 'POST',
url: requestUrl,
data: JSON.stringify(options.data || {}),
dataType: 'json',
contentType: 'application/json; charset=utf-8',
success: function(result, status, xhr) {
// ******** 判断响应头是否包含lj_encrypt字段,若包含则解密数据 ********
const needEncrypt = xhr.getResponseHeader('lj_encrypt');
// 判定规则:字段存在且值为true/1/yes等(兼容常见的真值)
const isNeedDecrypt = needEncrypt && ['true', '1', 'yes'].includes(needEncrypt.toLowerCase());
if (isNeedDecrypt) {
// 需要解密时,先获取AES密钥
getSecretKey(function(secretKey) {
if (!secretKey) {
options.error && options.error({
code: -1,
msg: '获取解密密钥失败,无法解密数据'
});
return;
}
try {
// 区分数据类型:若为字符串则直接解密,若为对象则针对data字段解密(根据实际业务调整)
let decryptedResult = {};
if (typeof result === 'string') {
// 字符串类型直接解密
decryptedResult = JSON.parse(aesDecrypt(result, secretKey));
} else {
// 对象类型,通常加密的是data字段(可根据实际业务调整)
decryptedResult = {
...result,
data: result.data ? JSON.parse(aesDecrypt(result.data, secretKey)) : result.data
};
}
// 处理解密后的业务逻辑
handleBusinessResult(decryptedResult, options);
} catch (error) {
console.error('数据解密后处理失败:', error);
options.error && options.error({
code: -2,
msg: '数据解密后解析失败'
});
}
});
} else {
// 不需要解密,直接处理业务结果
handleBusinessResult(result, options);
}
},
error: function(xhr, status, error) {
// 网络错误处理
let errorMsg = '';
switch (status) {
case 'timeout':
errorMsg = '请求超时,请检查网络';
break;
case 'error':
errorMsg = '网络错误,请检查网络连接';
break;
case 'abort':
errorMsg = '请求被中断';
break;
default:
errorMsg = '请求失败:' + error;
break;
}
options.error && options.error({
code: xhr.status,
msg: errorMsg,
original: error
});
}
});
}
/**
* 统一处理业务结果(成功/失败)
* 抽离公共逻辑,避免代码冗余
* @param {Object} result - 业务返回结果
* @param {Object} options - AJAX请求配置
*/
function handleBusinessResult(result, options) {
// 业务成功(code=0)
if (result.code === 0) {
options.success && options.success(result);
} else {
// 业务失败
options.error && options.error(result);
}
}
</script>
</body>
</html>
通过用户token跳转到首页
- 网址:https://www.pointshow.net/#/auth?t=token&failurl=登录失败的跳转地址
- 逻辑:token有效则跳转到首页,无效则跳转回failurl