node.js基於express框架搭建一個簡單的註冊登陸Web功能

這個小應用使用到了node.js  bootstrap  express  以及數據庫的操做 :使用mongoose對象模型來操做 mongodbjavascript

若是沒了解過的能夠先去基本瞭解一下相關概念~php

 

首先註明一下版本,由於express由於版本的不一樣使用的方式也不一樣,我這算是目前最新的了吧css

尚未裝express的能夠移步到 這裏 看看express框架的獲取安裝html

 

1.簡單地項目初始化java

進入你的nodejs安裝路徑下邊,如圖,而後執行命令  express -e test  (這裏把項目名設置爲test)node

出現如上圖所示,看到install dependencies沒有,它說若是你想安裝依賴就先進入項目test目錄,而後執行 npm install安裝依賴模塊。jquery

那就開始吧,網絡環境差的可能安裝會出錯..出現很長一大串通常就好了git

如此一來,項目初始已經完成,能夠運行一下項目 npm start 看是否正常。github

 

 

ok 還算正常,下面先來基本分析一下生成的初始項目ajax

 

以前 那篇文章 已經說過 

項目建立成功以後,生成四個文件夾,主文件app.js與配置信息文件packetage.json

bin是項目的啓動文件,配置以什麼方式啓動項目,默認 npm start

public是項目的靜態文件,放置js css img等文件

routes是項目的路由信息文件,控制地址路由

views是視圖文件,放置模板文件ejs或jade等(其實就至關於html形式文件啦~)

express這樣的MVC框架模式,是一個Web項目的基本構成

 

先來看看文件信息package.json  通常項目的主要信息都會在這裏產生

複製代碼
複製代碼
{
  "name": "test",
  "version": "0.0.0",
  "private": true,
  "scripts": {
    "start": "node ./bin/www"
  },
  "dependencies": {
    "body-parser": "~1.12.0",
    "cookie-parser": "~1.3.4",
    "debug": "~2.1.1",
    "ejs": "~2.3.1",
    "express": "~4.12.2",
    "morgan": "~1.5.1",
    "serve-favicon": "~2.2.0"
  }
}
複製代碼
複製代碼

看看主文件 app.js   這是它的初始形式,這個模塊還要繼續導出給 bin文件夾下的www文件使用

複製代碼
複製代碼
 1 var express = require('express');
 2 var path = require('path');
 3 var favicon = require('serve-favicon');
 4 var logger = require('morgan');
 5 var cookieParser = require('cookie-parser');
 6 var bodyParser = require('body-parser');
 7 
 8 var routes = require('./routes/index');
 9 var users = require('./routes/users');
10 
11 var app = express();
12 
13 // view engine setup
14 app.set('views', path.join(__dirname, 'views'));
15 app.set('view engine', 'ejs');
16 
17 // uncomment after placing your favicon in /public
18 //app.use(favicon(__dirname + '/public/favicon.ico'));
19 app.use(logger('dev'));
20 app.use(bodyParser.json());
21 app.use(bodyParser.urlencoded({ extended: false }));
22 app.use(cookieParser());
23 app.use(express.static(path.join(__dirname, 'public')));
24 
25 app.use('/', routes);
26 app.use('/users', users);
27 
28 // catch 404 and forward to error handler
29 app.use(function(req, res, next) {
30   var err = new Error('Not Found');
31   err.status = 404;
32   next(err);
33 });
34 
35 // error handlers
36 
37 // development error handler
38 // will print stacktrace
39 if (app.get('env') === 'development') {
40   app.use(function(err, req, res, next) {
41     res.status(err.status || 500);
42     res.render('error', {
43       message: err.message,
44       error: err
45     });
46   });
47 }
48 
49 // production error handler
50 // no stacktraces leaked to user
51 app.use(function(err, req, res, next) {
52   res.status(err.status || 500);
53   res.render('error', {
54     message: err.message,
55     error: {}
56   });
57 });
58 
59 
60 module.exports = app;
複製代碼
複製代碼

www文件內容:這裏擁有着http服務器的基本配置

  View Code

再來介紹一下項目使用到的ejs模板,好比看看這個view裏邊的index.ejs (咱們待會能夠直接把它轉爲html,差很少的)

複製代碼
複製代碼
<!DOCTYPE html>
<html>
  <head>
    <title><%= title %></title>
    <link rel='stylesheet' href='/stylesheets/style.css' />
  </head>
  <body>
    <h1><%= title %></h1>
    <p>Welcome to <%= title %></p>
  </body>
</html>
複製代碼
複製代碼

<%= title %> 這就是ejs的使用範例,title的值經過路由routes文件夾下index.js代碼傳入(後面再談)

 

好了,基本介紹了項目的初始狀況

 

2.基於初始項目的改進-- 註冊登陸功能

 

設計以下:

一個初始界面(其實就是原始地址:好比 localhost:3000(index.html  路徑爲/ ) ,在初始界面選擇登陸或註冊

跳進來以後會先跳進登陸界面(login.html  路徑爲 /login),能夠選擇先註冊(跳轉 register.html  路徑爲/register)

跳進註冊界面後就會跳進(register.html 路徑爲 /register),註冊成功後就跳轉登陸界面(login.html  路徑爲 /login)

在登陸界面登陸成功後就跳轉(home.html  路徑爲 /home). 在home這裏還提供了註銷的功能(無頁面文件,它的路徑爲 /logout

若是瀏覽器直接輸入localhost:3000/home  要先判斷是否登陸成功,未登陸不容許進入

看到上訴,應該瞭解到:咱們是經過一個路徑,而後經過這個路徑的解析,從而渲染出這個路徑對應的模板文件,其中咱們這裏的模板文件爲.html後綴的

 

首先展現一下基本界面形態:

 

而後先註冊吧,點擊註冊

 

 

填入用戶名密碼,這裏稍微設置了兩次密碼相同的判斷,註冊成功它會自動跳轉登陸界面

用mongoVUE看看數據的建立

那就登陸吧,登陸成功跳轉home界面

註銷吧,註銷後清除session值,而後跳轉到根路徑

而後試一下瀏覽器直接進入 home路徑? 瀏覽器地址輸入  localhost:3000/home  回車, ok 它自動跳轉到登陸界面

 

 

 

 

好如今開始解析如何構建這個小項目:

由於咱們直接使用了後綴名 .html ,因此咱們要先修改一下ejs模板  ,再把原來views目錄下模板文件後綴改爲 .html

複製代碼
複製代碼
var app = express();

// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.engine("html",require("ejs").__express); // or   app.engine("html",require("ejs").renderFile);
//app.set("view engine","ejs");
app.set('view engine', 'html');
複製代碼
複製代碼

其實就是加一句再改一句。 __express 和renderFile均可以, 不用管它是什麼,它能那樣用就好了

 

而後咱們知道須要這些模板文件,那就建立它們吧

index.html    其中 <%= title %>使用到了模板  鏈接<a> 直接使用了路由路徑的方法

複製代碼
複製代碼
<!DOCTYPE html>
<html>
  <head>
    <title><%= title %></title>
    <link rel='stylesheet' href='/stylesheets/style.css' />
    <style type="text/css">
    a{margin-left: 20px; text-decoration: none;}
    a:hover{text-decoration: underline;}
    </style>
  </head>
  <body>
    <h1><%= title %></h1>
    <p>Welcome to <%= title %></p>
    
    <p><a href="/login">登陸 </a>
    <a href="/register">   註冊</a>
    </p>
  </body>
</html>
複製代碼
複製代碼

register.html  註冊方式主要是把原始 form表單 onsubmit="return false" 防止默認提交,而後在輸入信息正確的狀況下,經過ajax,把表單信息post到路徑/register

而後咱們就經過路由功能根據此路徑來處理信息(這個跟ajax和php交互是同一個道理)

複製代碼
複製代碼
<!DOCTYPE html>
<html lang="en">


<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width,initial-scale=1.0">
    <title><%= title %></title>
    <link rel="stylesheet" href="stylesheets/bootstrap.min.css" media="screen">
    <style type="text/css">
    .m15{ margin: 15px;}
     .tc{ text-align: center;font-size: 18px;font-weight: 600;}
    </style>
</head>
<body screen_capture_injected="true">
    <div class="container">
    <%- message %>
        <form class="col-sm-offset-4 col-sm-4 form-horizontal" role="form" method="post" onsubmit="return false">
            <fieldset>
                <legend></legend>
                <div class="panel panel-default">
                <div class="panel-heading">
                    <p class="tc">註冊信息</p>
                </div>
                <div class="panel-body m15">
                <div class="form-group">
                    <div class="input-group">
                        <span class="input-group-addon">
                        <span class="glyphicon glyphicon-user"></span>
                        </span>
                        <input type="text" class="form-control" id="username" name="username" placeholder="請輸入用戶名" required>
                    </div>
                </div>
                <div class="form-group">
                    <div class="input-group">
                        <span class="input-group-addon">
                        <span class="glyphicon glyphicon-lock"></span>
                        </span>
                        <input type="text" class="form-control" id="password" name="password" placeholder="請輸入密碼" required>
                    </div>
                </div>
                <div class="form-group">
                    <div class="input-group">
                        <span class="input-group-addon">
                        <span class="glyphicon glyphicon-lock"></span>
                        </span>
                        <input type="text" class="form-control" id="password1" name="password1" placeholder="請再次輸入密碼" required>
                    </div>
                </div>
                <div class="form-group">
                        <button type="submit" class="btn btn-primary btn-block" id="register1">註冊</button>
                </div>
                <div class="form-group">
                        <button type="button" class="btn btn-info col-sm-2 col-sm-offset-10" id="login1">登陸</button>
                </div>
                </div>
                </div>
            </fieldset>
        </form>
    </div>

    <script type="text/javascript" src="javascripts/jquery.min.js"></script>
    <script type="text/javascript" src="javascripts/bootstrap.min.js"></script>
    <script type="text/javascript">
    $(function(){ 
        $("#login1").click(function(){ 
            location.href = 'login';
        });
        $("#register1").click(function(){ 
            var username = $("#username").val();
            var password = $("#password").val();
            var password1 = $("#password1").val();
            if(password !== password1){ 
                $("#password").css("border","1px solid red");
                $("#password1").css("border","1px solid red");
            }else if(password === password1){
            var data = {"uname":username,"upwd":password};
            $.ajax({ 
                url: '/register',
                type: 'post',
                data: data,
                success: function(data,status){ 
                    if(status == 'success'){ 
                        location.href = 'login';
                    }
                },
                error: function(data,err){ 
                        location.href = 'register';
                }
            }); 
        }
        });
    });
</script>
</body>
</head>
</html>
複製代碼
複製代碼

login.html   跟上面register.html原理差很少

複製代碼
複製代碼
<!DOCTYPE html>
<html lang="en">


<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width,initial-scale=1.0">
    <title><%= title %></title>
    <link rel="stylesheet" href="stylesheets/bootstrap.min.css" media="screen">
    <style type="text/css">
    .m15{ margin: 15px;}
     .tc{ text-align: center;font-size: 18px;font-weight: 600;}
    </style>
</head>
<body screen_capture_injected="true">
    <div class="container">
    <%- message %>
        <form class="col-sm-offset-4 col-sm-4 form-horizontal" role="form" method="post" onsubmit="return false">
            <fieldset>
                <legend></legend>
                <div class="panel panel-default">
                <div class="panel-heading">
                    <p class="tc">請先登陸</p>
                </div>
                <div class="panel-body m15">
                <div class="form-group">
                    <div class="input-group">
                        <span class="input-group-addon">
                        <span class="glyphicon glyphicon-user"></span>
                        </span>
                        <input type="text" class="form-control" id="username" name="username" placeholder="請輸入用戶名" required>
                    </div>
                </div>
                <div class="form-group">
                    <div class="input-group">
                        <span class="input-group-addon">
                        <span class="glyphicon glyphicon-lock"></span>
                        </span>
                        <input type="text" class="form-control" id="password" name="password" placeholder="請輸入密碼" required>
                    </div>
                </div>
                <div class="form-group">
                        <button type="submit" class="btn btn-primary btn-block" id="login0">登陸</button>
                </div>
                <div class="form-group">
                        <button type="button" class="btn btn-info col-sm-2 col-sm-offset-10" id="register0">註冊</button>
                </div>
                </div>
                </div>
            </fieldset>
        </form>
    </div>

    <script type="text/javascript" src="javascripts/jquery.min.js"></script>
    <script type="text/javascript" src="javascripts/bootstrap.min.js"></script>
    <script type="text/javascript">
    $(function(){ 
        $("#register0").click(function(){ 
            location.href = 'register';
        });
        $("#login0").click(function(){ 
            var username = $("#username").val();
            var password = $("#password").val();
            var data = {"uname":username,"upwd":password};
            $.ajax({ 
                url:'/login',
                type:'post',
                data: data,
                success: function(data,status){ 
                    if(status == 'success'){ 
                        location.href = 'home';
                    }
                },
                error: function(data,status){ 
                    if(status == 'error'){ 
                        location.href = 'login';
                    }
                }
            });
        });
    });
    </script>
</body>
</head>
</html>
複製代碼
複製代碼

最後是 home.html    裏頭的 user.name 就是使用ejs模板經過session.user來獲取user對象,這裏user有name和password的屬性

複製代碼
複製代碼
<!DOCTYPE html>
<html>
  <head>
    <title><%= title %></title>
    <link rel='stylesheet' href='/stylesheets/style.css' />
    <style type="text/css">
    a{margin-left: 20px; text-decoration: none;}
    a:hover{text-decoration: underline;}
    </style>
  </head>
  <body>
  <h1>Your name:   <%- user.name %></h1>
    <p>Welcome to your home ~</p>
    <p><a href="/logout">我要註銷 </a>
    </p>
  </body>
</html>
複製代碼
複製代碼

模板文件就是這些,接下來給主文件 app.js增長路由配置,讓瀏覽器訪問到路徑後得以被解析

複製代碼
app.use('/', routes);  // 即爲爲路徑 / 設置路由
app.use('/users', users); // 即爲爲路徑 /users 設置路由
app.use('/login',routes); // 即爲爲路徑 /login 設置路由
app.use('/register',routes); // 即爲爲路徑 /register 設置路由
app.use('/home',routes); // 即爲爲路徑 /home 設置路由
app.use("/logout",routes); // 即爲爲路徑 /logout 設置路由
複製代碼

app.use是一箇中間件的用法,這裏的routes看初始項目的那句代碼,就是引用了routes文件夾下的index.js模塊

var routes = require('./routes/index');
var users = require('./routes/users');

因此待會咱們還得繼續修改完善index.js(我這裏是直接把全部路徑的處理方法所有放到index.js中,實際作的時候能夠考慮細分出模塊)

這裏先不說index.js,由於還有不少更寬泛的工做沒弄

 

  1.註冊登陸,因此咱們得須要數據庫

這裏使用到了mongodb . 據我所知mongodb主要有兩種使用方法,這裏使用了其中的一種:使用 mongoose

  Mongoose是MongoDB的一個對象模型工具,是基於node-mongodb-native開發的MongoDB nodejs驅動,能夠在異步的環境下執行。

同時它也是針對MongoDB操做的一個對象模型庫,封裝了MongoDB對文檔的的一些增刪改查等經常使用方法,讓NodeJS操做Mongodb數據庫變得更加靈活簡單。

咱們經過Mongoose去建立一個「集合」並對其進行增刪改查,就要用到它的三個屬性:Schema(數據屬性模型)、Model、Entity

  這裏簡單介紹一下,更詳細的用法能夠自行查閱~

Schema —— 一種以文件形式存儲的數據庫模型骨架,沒法直接通往數據庫端,也就是說它不具有對數據庫的操做能力,僅僅只是數據庫模型在程序片斷中的一種表現,能夠說是數據屬性模型(傳統意義的表結構),又或着是「集合」的模型骨架。

好比定義一個Schema:

複製代碼
複製代碼
var mongoose = require("mongoose");
 
var TestSchema = new mongoose.Schema({
    name : { type:String },//屬性name,類型爲String
    age  : { type:Number, default:0 },//屬性age,類型爲Number,默認爲0
    time : { type:Date, default:Date.now },
    email: { type:String,default:''}
});
複製代碼
複製代碼

Model —— 由Schema構造生成的模型,除了Schema定義的數據庫骨架之外,還具備數據庫操做的行爲,相似於管理數據庫屬性、行爲的類。

好比定義一個Model:

var db = mongoose.connect("mongodb://127.0.0.1:27017/test");
 
// 建立Model
var TestModel = db.model("test1", TestSchema);

Entity —— 由Model建立的實體,使用save方法保存數據,Model和Entity都有能影響數據庫的操做,但Model比Entity更具操做性。

好比定義一個Entity

複製代碼
複製代碼
var TestEntity = new TestModel({
       name : "Lenka",
       age  : 36,
       email: "lenka@qq.com"
});
console.log(TestEntity.name); // Lenka
console.log(TestEntity.age); // 36
複製代碼
複製代碼

 

基本就介紹到這裏

由於咱們要使用數據庫,那就來建立它。使用的就是上述的方法

首先,在項目根目錄下創建一個database文件夾,創建文件 models.js  而後創建model處理文件 dbHandel.js

寫入文件 models.js  一個user集合,裏面有name和password屬性

複製代碼
module.exports = { 
    user:{ 
        name:{type:String,required:true},
        password:{type:String,required:true}
    }
};
複製代碼

寫入文件 dbHandel.js  裏邊主要是獲取 Schema 而後處理獲取 model ,最後就是返回一個model了(提供其餘文件對model的操做 -- Entity是使用)

複製代碼
複製代碼
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var models = require("./models");

for(var m in models){ 
    mongoose.model(m,new Schema(models[m]));
}

module.exports = { 
    getModel: function(type){ 
        return _getModel(type);
    }
};

var _getModel = function(type){ 
    return mongoose.model(type);
};
複製代碼
複製代碼

創建好基本文件後咱們就在app.js中調用使用它:要使用multer和mongoose模塊

項目沒有,因此咱們要安裝

app.js中加上

複製代碼
複製代碼
var multer = require('multer');
var mongoose = require('mongoose');

global.dbHandel = require('./database/dbHandel');
global.db = mongoose.connect("mongodb://localhost:27017/nodedb");

// 下邊這裏也加上 use(multer())
app.use(bodyParser.urlencoded({ extended: true }));
app.use(multer());
app.use(cookieParser());
複製代碼
複製代碼

 

  2.由於咱們使用到了session(好比進入home的時候判斷session值是否爲空),因此須要express-session 模塊

而後在app.js中引用它並做初始設置:

複製代碼
複製代碼
var session = require('express-session');

var app = express();
app.use(session({ 
    secret: 'secret',
    cookie:{ 
        maxAge: 1000*60*30;
    }
}));

app.use(function(req,res,next){ 
    res.locals.user = req.session.user;   // 從session 獲取 user對象
    var err = req.session.error;   //獲取錯誤信息
    delete req.session.error;
    res.locals.message = "";   // 展現的信息 message
    if(err){ 
        res.locals.message = '<div class="alert alert-danger" style="margin-bottom:20px;color:red;">'+err+'</div>';
    }
    next();  //中間件傳遞
});
複製代碼
複製代碼

好如今想一想咱們還剩下什麼:

數據庫已經提供出model接口給咱們使用(給它填數據)

已經初始化了路徑處理

初始化了session信息 數據庫配置等

頁面模板也已經作完

 

因此剩下的就是路徑處理的部分:去routes目錄下 修改index.js吧

/  路徑

複製代碼
/* GET index page. */
router.get('/', function(req, res,next) {
  res.render('index', { title: 'Express' });    // 到達此路徑則渲染index文件,並傳出title值供 index.html使用
});
複製代碼

/login 路徑

複製代碼
複製代碼
/* GET login page. */
router.route("/login").get(function(req,res){    // 到達此路徑則渲染login文件,並傳出title值供 login.html使用
    res.render("login",{title:'User Login'});
}).post(function(req,res){                        // 今後路徑檢測到post方式則進行post數據的處理操做
    //get User info
     //這裏的User就是從model中獲取user對象,經過global.dbHandel全局方法(這個方法在app.js中已經實現)
    var User = global.dbHandel.getModel('user');  
    var uname = req.body.uname;                //獲取post上來的 data數據中 uname的值
    User.findOne({name:uname},function(err,doc){   //經過此model以用戶名的條件 查詢數據庫中的匹配信息
        if(err){                                         //錯誤就返回給原post處(login.html) 狀態碼爲500的錯誤
            res.send(500);
            console.log(err);
        }else if(!doc){                                 //查詢不到用戶名匹配信息,則用戶名不存在
            req.session.error = '用戶名不存在';
            res.send(404);                            //    狀態碼返回404
        //    res.redirect("/login");
        }else{ 
            if(req.body.upwd != doc.password){     //查詢到匹配用戶名的信息,但相應的password屬性不匹配
                req.session.error = "密碼錯誤";
                res.send(404);
            //    res.redirect("/login");
            }else{                                     //信息匹配成功,則將此對象(匹配到的user) 賦給session.user  並返回成功
                req.session.user = doc;
                res.send(200);
            //    res.redirect("/home");
            }
        }
    });
});
複製代碼
複製代碼

 

/register 路徑

複製代碼
複製代碼
/* GET register page. */
router.route("/register").get(function(req,res){    // 到達此路徑則渲染register文件,並傳出title值供 register.html使用
    res.render("register",{title:'User register'});
}).post(function(req,res){ 
     //這裏的User就是從model中獲取user對象,經過global.dbHandel全局方法(這個方法在app.js中已經實現)
    var User = global.dbHandel.getModel('user');
    var uname = req.body.uname;
    var upwd = req.body.upwd;
    User.findOne({name: uname},function(err,doc){   // 同理 /login 路徑的處理方式
        if(err){ 
            res.send(500);
            req.session.error =  '網絡異常錯誤!';
            console.log(err);
        }else if(doc){ 
            req.session.error = '用戶名已存在!';
            res.send(500);
        }else{ 
            User.create({                             // 建立一組user對象置入model
                name: uname,
                password: upwd
            },function(err,doc){ 
                 if (err) {
                        res.send(500);
                        console.log(err);
                    } else {
                        req.session.error = '用戶名建立成功!';
                        res.send(200);
                    }
                  });
        }
    });
});
複製代碼
複製代碼

 

/home  路徑

複製代碼
複製代碼
/* GET home page. */
router.get("/home",function(req,res){ 
    if(!req.session.user){                     //到達/home路徑首先判斷是否已經登陸
        req.session.error = "請先登陸"
        res.redirect("/login");                //未登陸則重定向到 /login 路徑
    }
    res.render("home",{title:'Home'});         //已登陸則渲染home頁面
});
複製代碼
複製代碼

 

/logout  路徑

複製代碼
/* GET logout page. */
router.get("/logout",function(req,res){    // 到達 /logout 路徑則登出, session中user,error對象置空,並重定向到根路徑
    req.session.user = null;
    req.session.error = null;
    res.redirect("/");
});
複製代碼

 

固然了,把因此路徑的處理放在同一個index.js事實上有點糟糕,能夠考慮分着寫:(這裏提供一種思路分出模塊)

好比一個home.js模塊裏邊:

複製代碼
複製代碼
module.exports = function ( app ) {
    app.get('/logout', function(req, res){
        req.session.user = null;
        req.session.error = null;
        res.redirect('/');
    });
}
複製代碼
複製代碼

從而只須要在index.js模塊裏邊引用便可

module.exports = function ( app ) {
    require('./logout')(app);
};

在app.js模塊中再引用一下就能夠(routes目錄下index.js是默認文件,因此能夠省略index)

require('./routes')(app);

 

 

3.好了,一個簡單的註冊登陸功能已經完成了,啓動項目吧

(注意:由於要使用到mongodb數據庫,因此要先開啓數據庫服務,否則沒法訪問,由於咱們使用了nodedb 這個數據庫,因此最後也要先在mongodb中建立它,否則也有可能出錯 未安裝數據庫的能夠看看  這篇   ,檢測數據庫服務是否開啓:瀏覽器打開localhost:27017 就能訪問 ,而後給數據庫添加nodedb吧)

服務開啓

初始化nonedb能夠相似這樣

 

啓動項目,npm start 

上面那個bson錯誤的不用管它..我也不知咋處理,據說能夠直接 npm install bson 或者 npm update 就行

但我試了貌似沒什麼效果

 

好了,項目已經打開,瀏覽器輸入 localhost:3000 訪問吧 (期間能夠本身查看mongodb數據庫裏邊nodedb --> user 數據的改動,使用mongoVUE或者命令查看)

須要代碼的可移步至Github:  https://github.com/imwtr/nodejs_express_login_register

相關文章
相關標籤/搜索