分类 后端 下的文章

v2-9765e9b4343a30b5fb43b3a227888221_720w.png

数据类型

  1. string
  2. number
  3. boolean
  4. array
  5. hash

以上是常见的数据类型

命名规则

常量,全部是大写字母

VERSION = "v1"

普通变量,如果不算@,@@等,我们一般直接以小写字母表达就可以了

age = 12;
user_name = "seho";

class和module只需要首字母大写和驼峰表达即可

class Apple

方法名,小写字母开头,结束可以用=,?结尾

def show
end

def index=
end

class写法

在ruby中,ruby的任何变量,string,number等都是object

class Api
  def show
    @article = "hello";
    color = "red";
    render_ajax_success({ msg: "123", text: color, age: age, name: name })
  end

  def index
    @article = "hello"
  end
end

名字首字母大写;class开头,end结尾;文件名称和类名一样,只不过改成小写和_拼接

如果我们要声明一个成员变量在类中,我们有一个更简单的方法去声明,并且自动写好了get和set方法:

attr_accessor "color"
attr_accessor "age"
attr_accessor "name"

类中同样有一个initialize方法,在initialize中我们可以定义class被实例化之后做的事情

同样的,ruby也有私有和公有方法

private
def getName

end

private标注一个方法为私有,那么没有标注的就是公有方法,在class中,一般私有方法是在公有方法的下方

赋值

ruby不像java,赋值需要声明一个变量的类型,直接赋值即可

age = 13;
name = "seho";

插值表达式

在js中,我们可以使用+来拼接变量达到组合的目的,ruby同样也可以

age = "13"
name = "seho"
puts age + name;

但是如果两个不同类型的变量拼接就会报错,因为ruby不知道如何转换,这个时候就需要用到插值表达式了

age = 13
puts "name: #{age}"

各种变量

类变量

# 多个实例都会共享的变量
@@age = 123;
A.new.@@age; # 123
B.new.@@age; # 123

实例变量

# 作用域是class类中,在ruby中广泛使用
@name = “seho”;

普通变量

# 作用域是在方法内
name = "seho"

全局变量

# 全局都可以使用的变量,不常用
$version = "v1";

方法

类方法

class A
    def A.show
    @article = "hello";
  end
end

实例方法

class A
    def show
    @article = "hello";
  end
end

他们的调用方法不一样

A.show
A.new.show

symbol

js在es6也有这个数据类型,ruby也表示内容永远不变,一般用于表达hash的key,声明只需要前面加冒号就可以了。

:age = number;
# 声明一个hash,最需要用到symbol
time = {
    :name: "seho"
}

数组

num = [1,2,3,4]
# 数据类型混合着来
test = ["1",2, "1", :name, {:name: "seho"}]

Hash

hash有3种写法

jim = {
    :name => "jim",
    :age => 20
}
# 1.9之后产生的新写法
jim = {
    name: "hello",
    age: 20
}
# 也可以写成
jim = {}
jim[:name] = "jim"
jim[age] = 20

hash的key,string和symbol不同,例如

jim = {
    :name: 1,
    name: 2
}

a[:name] #=> 1
a["name"] #=> 2

条件判断语句

# if else end是最常见的
a = 1;
if a == 1
    puts "a is 1"
elseif a == 2
    puts "a is 2"
else
    puts "is not"
end

同样还有case分支语句

a = 1;
case a
    when 1 then puts "a is 1"
    when 3,4,5 then puts "a is [3,4,5]"
    else puts "a is not in there"
end

三元表达式

a = 1
puts a == 1 ? "one" : "not one"

循环

# for
[1,2,3].each { |e| 
    puts e;
}

# 等同于下方
for e in [1,2,3]
    puts e
end

for和each几乎一样,for是关键字,而each是数组的一个函数;高手都用each,因为ruby和js有相像之处,for中的e变量,是全局的,循环之外,仍然可以访问到它,而each可以说是局部变量;

# loop
loop do
 # code
 break if 表达式
end

# while
begin
 # code
end while 表达式

更推荐使用loop,因为可读性更强,我不熟悉ruby,我都要使用loop是因为它能给我带来最直观的语义,而while的语义明显拗口。

剑指题解


互联网人题解神器,神器在手,offer我有

随手刷题 | 无广告绿色免费 | 个性推荐 | 每日自动推送 | 多维度丰富领域知识

项目的起源是一个 github 仓库,名为前端知识每日 3+1,正是这个优秀的题库项目才有了我们团队开发聚合题库的想法。在我们国内程序员圈子,大多数小伙伴为了更好的技术发展和 offer,都通过训练题来提高自己的眼界,其中不乏包括算法/业务/软技能的题目,但是因为我们的环境和侧重点不一样,所以我们接触的项目和题目都不一样,而市面上很少有一个项目能做到,涵盖不同圈子的聚合题库。 《剑指题解》项目就是一个优秀的互联网行业聚合题库神器。


开源免费不易,请关注我们的公众号以及开源仓库

沈昊Seho/InternetQuestionBank<br/>
Github 点 star, 拉取代码时请选择最新分支
Dcloud 插件市场支持我们

目录

特点

  1. 汇聚优秀的开源题目,以技术社区为主线展开的刷题 APP
  2. 根据用户的喜好,去推送相关的最新题目,不会再刷到 “牛头不对马嘴”无用的题目
  3. 我们绑定了服务号,每日定时推送给用户最新的题目,让你能在每天清晨就开启活力新一天
  4. 刷题 APP 无广告,无硬性推广,一切盈利皆在赞助/官网流量
  5. 我们还有很多有意思的待开发 feature,比如大佬带萌新模式,还将开启校园/企业的通道,让每一个学生/员工都能每时每刻提高自己
  6. 剑指题解开源项目,从原型图到小程序,UI 图,UML,API 文档,第三方 API 对接说明书,使用说明都将免费开源,欢迎各位同僚二开,让这个行业变得更好,我们将用心地辅助你们解决在技术上的难题。

技术栈

关于技术栈的选型可以参考我们的语雀文档
周边物料的开源,还需要感谢 mockPlus 以及语雀:

<img class="imgIcon" src="http://static.yinzhuoei.com/typecho/2021/01/17/69752764374919.png" width="10%">


<img class="imgIcon" src="http://static.yinzhuoei.com/typecho/2021/01/17/697832282748067.jpg" width="20%"></img>

UML

由于此部分在开发新 feature 的工作中可能会进行变更或者本就有实质性的错误,欢迎向我们反馈错误,我们将感激不尽。

类图

拉取任务

更多

更多相关资料请移步
out/doc或者语雀-UML

设计概览



设计源文件请移步:点击查看-为了您的体验,请务必下载最新版本的设计图
原型图在线预览: 点击查看, 如果失效请联系我们

API

《剑指题解》团队在 API 层面,开源的内容非常有限制,因为涉及到多位合作伙伴的题库授权,所以如果您要使用聚合题库 API 请联系我们商议,如果您是学习者,那么在语雀中的 API 文档会帮助到您,在这里我们将简单介绍。

uniapp 的云函数开发,我们是第一次接触,所以在此之前我们做了很多调研,在下文也提到了关于 explain.js 这个框架的作用,我们也是非常感谢 uniapp 开源社区有这样创作质量的第一批的 unicloud 框架开发者,在咨询了官方人员之后,我们了解到如果要使用 ts 来开发,需要把 ts 编译成 js 来放在云函数目录,所以我们选择了以下的技术栈,开发者可以很爽的使用 ts 来开发 unicloud 云函数:

  • esbuild-node-tsc
  • nodemon

esbuild-node-tsc 这个工具是基于 esbuild 的,利用其特性,我们可以比 ts 官方提出的 tsc 编译工具更快,快几十倍到上百倍,这也是得益于 esbuild 的使用 go 语言直接编译的特性。

nodemon 可以帮助我们监听目录文件,一旦改变就执行打包 ts 命令直接将编译好的 js 放在指定目录
所以我们把 explain.js 的配置也相应的改变,我们在 services 目录中存储的是我们 ts 文件,在 dist 中存储的是我们编译好的 js 文件,而 dist 目录就是 explain 需要的:

config.init({
  baseDir: __dirname,
  serviceDir: "/dist/",
});

需要注意的事情是:
由于云函数大小限制,我们需要把相关依赖全部安装到全局,包括 typescript

读写职责分离

读写职责分离模式(CQRS)是一种把查询(Queries) 数据和和更新(Commands) 数据通过使用各自独立的接口分开的模式。
Uniapp 的 Unicloud 很好,其中的 jql 也是大开眼界,确实统一了前端开发操作数据库的体验,让我们前端开发操作数据库非常友好,但是我们使用 CQRS 将这些原本封装的很好的 API 不予以使用。

  • 前端使用 jql 去读取想要的数据
  • 写入操作就交给云函数去执行

如图,前端读取一个列表数据

const db = uniCloud.database();
const databaseName = "testInit";
// 获取测试数据列表
export function getTestList() {
  return new Promise((resolve) => {
    db.collection(databaseName)
      .get()
      .then((res) => {
        // res 为数据库查询结果
        resolve(res);
      })
      .catch((err) => {
        // err.message 错误信息
        // err.code 错误码
      });
  });
}

我们将这样的文件抽出了一个一个查询模块,每一个模块对应了一个数据表(这里是 testInit)封装在了 API 这个文件夹中。

Vue 页面将这样去调用,这样我们在 API 查询层将做好数据的处理,确保 Vue 拿到的是可以直接渲染的干净数据。

import { getTestList } from "../../api/test";
const data = getTestList();

我们写入操作将通过云函数直接调用,唯一设计不同的地方在于,我们将云函数这个概念改变了,应该是模块化,我们把一个一个函数变成了模块。

在我们的初步技术调研过程中,由于 Uni 官方并没有提供给用户云函数开发框架,但是我们在插件市场中找到了一款名为 explain 的开发框架,它可以迅速的帮助我们实现 restapi 风格的单路由云函数,这款框架我们不多做介绍,文档在这里:explain.js 快速开发 uni 云函数的框架

// 注册用户根据手机号
  addUserByPhone() {
    return handleMustRequireParam(
        [{
            key: "username",
            value: "用户名",
          },
          {
            key: "password",
            value: "密码",
          },
        ],
        this.event.params
      )
      .then(async () => {
        const {
          username,
          password
        } = this.event.params;
        if (!/^1[3456789]\d{9}$/.test(username)) {
          return appErrorMessage("用户名格式不正确");
        } else if (password === "" || password.length < 6) {
          return appErrorMessage("密码格式不正确");
        } else {
          // 校验手机号
          return await uniID.register({
            username,
            password,
          });
        }
      })
      .catch((err) => {
        return err;
      });
  }

那么我们如果要调用 testPrint 这个模块中的增加操作

uniCloud.callFunction({
  name: "application",
  data: {
    route: "api/user",
    method: "POST",
    params: {
      username: "18291563764",
      password: "sas",
    },
  },
});

贡献者

感谢为《剑指题解》这个优秀项目贡献自己一份力的小伙伴们:

如果您想加入到我们的贡献者队列中,请联系我们,这里还有相关贡献者的介绍,希望对您有帮助

核心维护者

相关开源物料

题库合作伙伴

QQ 群和钉钉群欢迎大家加入


许可

Apache License © 剑指题解
如果您在其他有关我们的文档见到了与之不符合的协议内容,请联系我们,这可能是我们的历史遗留的代码问题。

Nginx是一款高性能服务器,最近这几年非常火,以轻量且高并发,高性能著称,那么此笔记将不会从0开始讲解API,而是会从各种问题入手,通过问题学习nginx。


特点:

  1. IO多路复用
  2. 高性能
  3. 高并发
  4. 占用系统资源少

Untitled.png

Nginx作为一个WEB服务器,有着大好的未来,市场份额非常给力,同时也是份额上升速度最快的web服务器。

Untitled.png

Nginx作为前端来说,需要学习什么?我们只需要学习Nginx在应用部署,反向代理,处理资源的进程,亦或者是搭建网站的基础知识,如果你还没有一个blog,那么就从现在开始学习nginx并且搭建你的第一个网站吧。


反向代理与正向代理

Untitled.png

我们在平时上外网的时候,比如谷歌,youtube,twitter,Ins等,如果使用我们内地网络,是访问不成功的,只有在香港台湾或者境外才能访问到类似的外网。那我们需要通过内地网络去访问外网只能通过一个proxy代理去做一个请求的转发,我们的内地网络请求在到达外网地址之前,会经过一层代理,这个代理会去请求外网,请求成功之后会把页面呈现给我们的客户端。

在这个过程中,外网服务器不知道我们的内地网络是谁,只知道代理地址,所以对于外网服务器来说,请求的真实客户端是看不到的。那么这个过程就叫做 正向代理,proxy代理的是客户端。

Untitled.png

反向代理是相反的,代理的是服务端,对于客户端而言,访问的服务器仅仅是多个真实服务器的一个代理而已,所以对于客户端用户而言,真实服务器的信息是不可见的。这样的过程也就是反向代理,proxy代理的是服务端

Nginx如何去做反向代理?

server{
        listen 80;
        server_name nginx.yinzhuoei.com;
        location / {
               proxy_pass http://yinzhuoei.com;
        }
}

其他的proxy配置:

proxy_set_header :在将客户端请求发送给后端服务器之前,更改来自客户端的请求头信息。
proxy_connect_timeout:配置Nginx与后端代理服务器尝试建立连接的超时时间。
proxy_read_timeout : 配置Nginx向后端服务器组发出read请求后,等待相应的超时时间。
proxy_send_timeout:配置Nginx向后端服务器组发出write请求后,等待相应的超时时间。
proxy_redirect :用于修改后端服务器返回的响应头中的Location和Refresh。

解决跨域

通过反向代理解决跨域:

server
{
   listen 3003;
   server_name localhost;
      ##  = /表示精确匹配路径为/的url
   location = / {
       proxy_pass http://localhost:5500;
   }
   ##  若 proxy_pass最后为/ 如http://localhost:3000/;匹配/no/son,则真实匹配为http://localhost:3000/son
   location /no {
       proxy_pass http://localhost:3000;
   }
   ##  /ok/表示精确匹配以ok开头的url,/ok2是匹配不到的,/ok/son则可以
   location /ok/ {
       proxy_pass http://localhost:3000;
   }
}

加header头允许跨域:

server
{
    listen 3002;
    server_name localhost;
    location /ok {
        proxy_pass http://localhost:3000;

        #   指定允许跨域的方法,*代表所有
        add_header Access-Control-Allow-Methods *;

        #   预检命令的缓存,如果不缓存每次会发送两次请求
        add_header Access-Control-Max-Age 3600;
        #   带cookie请求需要加上这个字段,并设置为true
        add_header Access-Control-Allow-Credentials true;

        #   表示允许这个域跨域调用(客户端发送请求的域名和端口) 
        #   $http_origin动态获取请求客户端请求的域   不用*的原因是带cookie的请求不支持*号
        add_header Access-Control-Allow-Origin $http_origin;

        #   表示请求头的字段 动态获取
        add_header Access-Control-Allow-Headers 
        $http_access_control_request_headers;

        #   OPTIONS预检命令,预检命令通过时才发送请求
        #   检查请求的类型是不是预检命令
        if ($request_method = OPTIONS){
            return 200;
        }
    }
}

Master&Woker模式

Nginx启动之后,启动了80端口进行服务监听,那么进程中就存在一个Mater主进程和多个Woker进程;

Untitled.png

Master进程的作用就是:读取&验证nginx.conf配置文件并且管理多个woker进程;接受外部信号;监控Woker,如果Woker挂掉,将自动重启Woker;

Woker进程的作用就是:多个Woker会拦截所有的请求并做出处理;每一个woker进程维护一个线程;woker的个数和CPU有关,从nginx.conf配置woker个数,配置几个就是几个,但是要避免配置过多,要充分利用CPU;

一个请求到响应的流程:

  1. Nginx启动,Matster进程根据nginx.conf初始化;初始化监听socket;fork出多个woker进程;
  2. 发起请求
  3. woker进程们一起竞争,胜出者通过三次握手,建立socket连接,处理请求。

如何做热部署呢?

热部署就和前端热部署一样的性质,即修改配置文件,不需要重启服务器就可以使用最新的配置。

nginx -s reload

通过这样的一个命令即可热部署,无需重启,随时改随时用。

一般情况下,我们做热部署可以有几个方案,比如前端,webpack的本地开发工具,webpack-dev-server,即本地启动一个服务,开启一个websocket,当我们的文件改动,就重新加载这个css/js。

而nginx也是同样的方式么?我们的主进程master去发布一个修改请求,然后woker去订阅这个消息,实现类似这样的热部署?

其实不然,nginx使用的是如下的方案,当master监听到配置文件的更改,会创建一批新的woker去执行新的请求,老的woker进程会在任务处理完毕之后,再由master杀掉进程。


如何做到高并发?

Nginx采用多进程+异步非阻塞方式(IO多路复用):

关于异步和同步,我需要做一些概念上的整理;

同步和异步指的是消息的通信机制,我们做web开发是最能理解同步异步的区别的,因为我们天天和接口打交道;

1)所谓同步指的就是发起一个请求/调用,在没有得到结果之前就不会返回,一旦得到结果就立即返回;

2)所谓异步指的就是发起一个请求/调用,调用者不会主动去care被调用者,而被调用者拿到结果之后会通知调用者

而阻塞非阻塞指的是程序在等待调用结果时的状态

1)阻塞调用指的就是,结果返回之前当前线程被挂起,调用线程在返回之后才返回;那么挂起的这个线程是会被阻塞的;

2)非阻塞调用指的就是,不能立刻得到结果之前,线程是不会被挂起的,仍然可以做其他事情;那么非阻塞调用如何知道得到结果了呢,需要定时去check的;

关于阻塞IO和非阻塞IO等我总结完了再说哈,还有关于Nginx的IO多路复用Epoll模型,这个是延申知识了,我也需要学习整理哈,现在还不清楚这一块的东西。

Nginx后续章节过段时间发,中间要发几篇shadowDom和剑指题解的文章,大家耐心等待...


学习资料如下:

nginx如何做到高并发?
8分钟带你深入浅出搞懂Nginx
理解同步异步&阻塞非阻塞

微信截图_20210331210548.png

u=2703915976,3722517054&fm=26&gp=0.jpg

品优购:

我们需要做一个功能就是完成商品的分类,需要操作的pojo是itemcat,我们的根分类的id是0,所以我们现在的需求是当我们点击哪一个的时候就要查询当前id的下级
所以我们的第一个service产生了,就是根据父类id查询子类:我们这样写:

/**

  * 根据父id查询对象
  */
 public List<TbItemCat> getItemByParentId(Long parentId) {
     TbItemCatExample example = new TbItemCatExample();
     Criteria createCriteria = example.createCriteria();
     // 匹配父ID
     createCriteria.andParentIdEqualTo(parentId);
     List<TbItemCat> selectByExample = itemCatMapper.selectByExample(example);
     return selectByExample;
 }

这样就我们能查询出来,action省略,我们来看看主要的前端代码:

初始化:ng-init="selectByParentId(0)"
传参为0,我们循环的是父级;

ng-click="findOne(entity.id)"

这样就可以在列表中点击哪个就出现哪个的分类了,我们这一部分完成了,现在的需求就是我们要完成面包屑的操作;

我们面包屑的层级是这样的,默认是父级是1,二级是2,三级是3;

然后我们定义一些方法:

1.首先是初始化层级和设置层级

//设置面包屑初始为0根目录

 $scope.grade = 1;
 //设置级别
 $scope.setGrade = function(value) {
     $scope.grade = value;
 }

2.判断当前的层级,如果层级是2,就把对应的参数传递层级2的对象中,然后通过这个对象中的id再查询

//读取列表

 $scope.selectGrade = function(p_entity) {
     //如果为一级
     if ($scope.grade === 1) {
          $scope.entity_1 = null;
          $scope.entity_2 = null;
     }
     if ($scope.grade === 2) {
          $scope.entity_1 = p_entity;
          $scope.entity_2 = null;
     }
     if($scope.grade === 3){
          $scope.entity_2 = p_entity;
     }
     
     $scope.selectByParentId(p_entity.id)
 }

然后我们的html可以这样写:

面包屑:

  • 顶级分类列表

  • {{entity_1.name}}

  • {{entity_2.name}}
  • 我们的列表的按钮:

    ng-click="setGrade(grade+1);selectGrade(entity)

    面包屑思路:当我们点击查询下一级的时候,会把层级关系加1,然后进行查询,查询的条件碰到了层级2,就会把它的对象变成entity_1,然后我们的面包屑上写的就是entity_1.name ; 所以我们就可以通过这样的方式来达到面包屑的效果。

    ----实现商品录入,在进行商品录入之前,我们需要知道2个概念:

    我们在写电商系统的时候,必须得要理解spu和sku是什么概念,具体就不阐述了,sku就是具体的商品列表,spu是商品的一些主干。
    这个商品的录入我们要结合2张表,一个是goods一个是goodsDesc,所以我们需要写一个组合表

    // 电商spu
     private TbGoods goods;
     // 商品拓展介绍
     private TbGoodsDesc goodsDesc;
     // 电商sku列表
     private List<TbItem> itemList;
    

    组合写好之后,我们需要更改mapper中,添加一个selectkey查询一下主键,这个主键的意图很简单,在添加商品成功之后,需要拿这个id做商品的拓展增加;

    所以我们的service层是这样写的:

    public void add(Goods goods) {

         // 增加商品记录
         // 设置未申请状态
         goods.getGoods().setAuditStatus("0");
         goodsMapper.insert(goods.getGoods());
         // 插入拓展信息,获取添加后的id
          goods.getGoodsDesc().setGoodsId(goods.getGoods().getId());
         // 添加
         goodsDescMapper.insert(goods.getGoodsDesc());
     }
    

    action同样省略(需要获取登陆名词并且set到里面去),我们这样就可以做好商品录入了,我们的前端进行对应的绑定即可,主要的是我们用到了一个富文本的编辑器:

    初始化,拿到editor对象,可以通过.html拿到里面的值进行添加到goodsDesc对象即可;

    FastDFS分布式文件管理服务器:

    我们开发过程中,可以只用一台服务器,这个服务器准备好了,运行开启即可,然后我们需要准备几个配置文件,一个是conf文件一个是简单封装了上传的文件
    具体的步骤我们可以看写的demo,不要忘记在springmvc中加入媒体解析器,上传后端代码:

     @RequestMapping("upload")
     public Result upload(MultipartFile file) {
         // 获取文件名
         String OriginalFilename = file.getOriginalFilename();
         // 截取文件的后缀
         String name = OriginalFilename.substring(OriginalFilename.lastIndexOf(".") + 1);
         try {
              util.FastDFSClient client = new FastDFSClient("classpath:config/fdfs_client.conf");
              String fileName = client.uploadFile(file.getBytes(), name);
              // 拼接图片url
              String url = images_server_url + fileName;
              return new Result(true, url);
         } catch (Exception e) {
              return new Result(false, "上传失败");
              // TODO: handle exception
         }
     }
    

    工具类在common中,util包中,需要加maven依赖才能引入,可以通过value注解来给images_server_url注入值,通过application.properties

    前端代码:

    this.upload = function() {

         //上传二进制文件需要的form对象
         var formData = new FormData();
         //添加文件到对象
         formData.append("file", file.files[0]); //name为file的第0个文件
         //执行上传,设置相关配置
         return $http({
              url : '../upload.do',
              method : "POST",
              //携带数据:
              data : formData,
              //设置头信息,如果不写,默认是json格式
              headers : {
                  'Content-Type' : undefined
              },
              //angular上传必须要写的:序列化
              transformRequest : angular.identity
         })
     }
    

    控制层:

    //上传图片

     $scope.upload = function() {
         $scope.imageEntity = {}
         uploadFile.upload().success(
              function(response) {
                  if (response.success) {
                       $scope.imageEntity.url = response.message
                  } else {
                       alert(response.message)
                  }
              }
         )
     }
     
     
     //定义实体结构
     $scope.entity = {goods:{},goodsDesc:{itemImages:[]}}
     //添加图片列表
     $scope.addImgList = function() {
          $scope.entity.goodsDesc.itemImages.push($scope.imageEntity)
     }
     //移出出图片列表
     $scope.deleImgList = function(index) {
          $scope.entity.goodsDesc.itemImages.splice(index, 1);
     }
    
    

    timg (1).jpg
    今天学习了这个SpringSecurity这个框架,记录一下注意的知识点和坑

    首先这个是一个安全框架,主要用途就是在登陆上,它为我们提供了很好的安全性和性能,为我们减少了很多开发需求

    我们可以花费一点点时间做一个入门小demo

    引入包和webxml加载配置我就不说了,都是复制粘贴的,最主要就是看我们的框架xml配置,我们需要了解一些功能。

    <?xml version="1.0" encoding="UTF-8"?>
    <beans:beans xmlns="http://www.springframework.org/schema/security"

     xmlns:beans="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsd

    http://www.springframework.org/schema/securityhttp://www.springframework.org/schema/security/spring-security.xsd">

     
     
     <!-- 以下页面不拦截 -->
     <http pattern="/login.html" security="none"></http>
     <http pattern="/login_error.html" security="none"></http>
     <!-- 页面拦截规则 -->
     <http use-expressions="false">
         <intercept-url pattern="/**" access="ROLE_USER"/>
         <!-- 开启表单自动生成 -->
         <form-login default-target-url="/index.html" authentication-failure-url="/login_error.html"/>
         <csrf disabled="true"/>
     </http>
     
     <!-- 认证管理器 -->
     <authentication-manager>
         <authentication-provider>
              <user-service>
                  <user name="admin" password="admin" authorities="ROLE_USER"/>
              </user-service>
         </authentication-provider>
     </authentication-manager>

    </beans:beans>

    我们的页面拦截规则的pattern主要是不拦截资源,比如我们的一些登陆页面或者错误页面,这些页面是不需要拦截的。
    我们拦截的规则设置成跟目录下的所有资源,匹配的用户是ROLE_USER , 框架必须指定前缀是ROLE,这里要注意,我们可以开启表单自动生成,然后成功跳转到首页,错误跳转
    到错误页面。

    我们的认证管理器,主要是写死账号和密码。

    这样就可以给我们提供了一个自动生成的,包括html页面都是框架帮助我们生成好了,我们通过了一个入门小案例知道了这个框架的大体运用。

    然后开始运用到项目中,引入依赖和webxml就不敲了,我们还是来熟悉一下业务场景,首先我们不能让框架帮助我们生成丑陋的登陆页面了,我们需要指定自己的登陆页面。
    我们可以这样写:

    <?xml version="1.0" encoding="UTF-8"?>
    <beans:beans xmlns="http://www.springframework.org/schema/security"

     xmlns:beans="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsd

    http://www.springframework.org/schema/securityhttp://www.springframework.org/schema/security/spring-security.xsd">

     <!-- 匹配不拦截的资源 -->
     <http pattern="/*.html" security="none"></http>
     <http pattern="/css/**" security="none"></http>
     <http pattern="/img/**" security="none"></http>
     <http pattern="/js/**" security="none"></http>
     <http pattern="/plugins/**" security="none"></http>
     <!-- 页面拦截规则 -->
     <http use-expressions="false">
         <intercept-url pattern="/**" access="ROLE_ADMIN" />
         <form-login login-page="/login.html" default-target-url="/admin/index.html"
              authentication-failure-url="/login.html" always-use-default-target="true" />
         <!-- 跨域拦截是否禁用 -->
         <csrf disabled="true" />
         <!--设置不拦截iframe框架内置 -->
         <headers>
              <frame-options policy="SAMEORIGIN" />
         </headers>
         <logout/>
     </http>
     <!-- 认证管理器 -->
     <authentication-manager>
         <authentication-provider>
              <user-service>
                  <user name="admin" password="123456" authorities="ROLE_ADMIN" />
              </user-service>
         </authentication-provider>
     </authentication-manager>
     

    </beans:beans>

    我们不拦截的资源就根据项目的场景来决定,我们在form中配置指定登陆页面,而且要加入一个成功默认跳转页面(很重要)
    认证管理器我们还是写死一个账号密码,让他可以登陆,这样我们就在管理员后台登陆的时候用到了这个安全框架,值得注意的是:

    前端html:action : /login 必须post方法 账号和密码的name必须是username和password

    然后我们开始做运营商的商家入驻申请其中非常关键的安全框架的查询数据库登陆+密码加密:

    查询数据库进行登陆,我们之前写的security写的是固定的admin和密码,我们如何查询数据库呢??

    我们需要自己在controller写一个包,在包下建立一个特殊的服务,我们在中配置扫描的是controller的包,所以我们这边的注入service是不起作用的通过注解
    因为这个服务是在doubbo远程注册,所以我们只能通过xml注入,我们先看看具体的查询服务:

    package com.pinyougou.service;

    import java.util.ArrayList;
    import java.util.Collection;
    import java.util.List;

    import org.springframework.security.core.GrantedAuthority;
    import org.springframework.security.core.authority.SimpleGrantedAuthority;
    import org.springframework.security.core.userdetails.User;
    import org.springframework.security.core.userdetails.UserDetails;
    import org.springframework.security.core.userdetails.UserDetailsService;
    import org.springframework.security.core.userdetails.UsernameNotFoundException;

    import com.pinyougou.pojo.TbSeller;
    import com.pinyougou.sellergoods.service.SellerService;

    /**

    • 认证类
      *
    • @author LAOSHEN
      *
      */
      public class UserDetailsServiceImpl implements UserDetailsService {

      // 此服务注入通过dubbo远程注入
      private SellerService sellerService;

      public void setSellerService(SellerService sellerService) {

        this.sellerService = sellerService;

      }

    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        // 添加角色集合
        List<GrantedAuthority> authorities = new ArrayList();
        authorities.add(new SimpleGrantedAuthority("ROLE_SELLER"));
        TbSeller seller = sellerService.findOne(username);
        // 判断用户是否存在
        if (seller != null) {
            // 判断用户是否通过审核
            if (seller.getStatus().equals("1")) {
                // 如果通过审核就生成改用户的正确密码与输入的进行匹配
                return new User(username, seller.getPassword(), authorities);
            } else {
                // 如果没有通过审核
                return null;
            }
        } else {
            // 如果没有此用户
            return null;
        }
    }

    }

    返回空就直接不能登陆成功,重定向到login页面中,最重要的是我们的安全框架的配置:

    <?xml version="1.0" encoding="UTF-8"?>
    <beans:beans xmlns="http://www.springframework.org/schema/security"

     xmlns:dubbo="http://code.alibabatech.com/schema/dubbo" xmlns:mvc="http://www.springframework.org/schema/mvc"
     xmlns:beans="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsd

    http://code.alibabatech.com/schema/dubbohttp://code.alibabatech.com/schema/dubbo/dubbo.xsd
    http://www.springframework.org/schema/securityhttp://www.springframework.org/schema/security/spring-security.xsd">

     <!-- 匹配不拦截的资源 -->
     <http pattern="/*.html" security="none"></http>
     <http pattern="/css/**" security="none"></http>
     <http pattern="/img/**" security="none"></http>
     <http pattern="/js/**" security="none"></http>
     <http pattern="/plugins/**" security="none"></http>
     <http pattern="/seller/add.do" security="none"></http>   //把一些业务逻辑的url进行放行,否则无法进行业务逻辑的操作
     <!-- 页面拦截规则 -->
     <http use-expressions="false">
         <intercept-url pattern="/**" access="ROLE_SELLER" />
         <form-login login-page="/shoplogin.html" default-target-url="/admin/index.html"
              authentication-failure-url="/shoplogin.html" always-use-default-target="true" />
         <!-- 跨域拦截是否禁用 -->
         <csrf disabled="true" />
         <!--设置不拦截iframe框架内置 -->
         <headers>
              <frame-options policy="SAMEORIGIN" /> 
         </headers>
         <logout/> //注销选项,页面通过访问/logout 即可注销
     </http>
     <!-- 认证管理器 -->
     <authentication-manager>
         <authentication-provider user-service-ref="UserDetailsService">
              <password-encoder ref="bcryptEncoder"></password-encoder>  //依赖于加密实现类,登陆的时候可以把原密码自动加密,无需将加密后的密码解密进行匹配
         </authentication-provider>
     </authentication-manager>
     
     <beans:bean id="UserDetailsService" class="com.pinyougou.service.UserDetailsServiceImpl">
         <beans:property name="sellerService" ref="sellerService"></beans:property>  //认证类:注入service接口,从dubbo中,需要拥有set方法别忘记了
     </beans:bean>
     
     <!-- 引用dubbo 服务 ,从注册中心去寻找接口服务并且注入-->
     <dubbo:application name="pinyougou-shop-web" />
     <dubbo:registry address="zookeeper://192.168.25.151:2181"/>
     <dubbo:reference id="sellerService" interface="com.pinyougou.sellergoods.service.SellerService"></dubbo:reference>  //从dubbo注册地址找service接口的服务,并且注册给认证类
     
     
     <beans:bean class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder" id="bcryptEncoder"></beans:bean>  //加密实现类

    </beans:beans>

    我们存入数据库的密码,可以通过在controller中这样定义:

    public Result add(@RequestBody TbSeller seller) {

         // 对密码进行加密存储到数据库
         BCryptPasswordEncoder passwordLock = new BCryptPasswordEncoder();
         String newPassWord = passwordLock.encode(seller.getPassword());
         seller.setPassword(newPassWord);
         try {
              sellerService.add(seller);
              return new Result(true, "增加成功");
         } catch (Exception e) {
              e.printStackTrace();
              return new Result(false, "增加失败");
         }
     }
    

    可以通过 BCrypt 类进行封装,让数据库存入加密之后的密码即可

    具体的可以看品优购的第四天的讲义。