LostKing - 后端
type: channel
link: http://47.107.87.11/tag/后端
items: Shiro框架实现登录验证和授权，以及客户端持久化登录
items: 登录系统的表设计和Shiro框架
items: 实现通过POST请求进行注册
items: SSM框架入门
items: Kafka消息队列和用户登录方案设计
items: 服务端技术架构和Nginx详解

Shiro框架实现登录验证和授权，以及客户端持久化登录
type: item
date: 2019-06-23T23:58:00+08:00
description: 二、大致内容实现用户登录和登录状态持久化，以及权限验证三、效果首现，通过运用shiro框架并设置好Fliter过滤器，并且设置好过滤规则，可以将所有请求都拦截，并且在配置之后，shiro框架会接管Tomcat的会话管理。因为HTTP请求是没有状态的，因此Tomcat自身会产生一个sessionID来对每个会话进行标记，而shiro框架对这一过程进行了接管，并能按自己的配置设置这个sessionID在cookie中的字段名。然后使用缓存，例如ehcache，redis等，进行这个sessionID的存储。我这里将其命名为sid。shiro框架中，请求经过过滤器后，会交给SecurityManager进行处理，而SecurityManager一般又从Realm中获取验证数据，所以我要结合我的数据接口的情况来自定义一个Realm，而它一般要实现两个功能，身份验证和权限授予，分别在doGetAuthorizationInfo()和doGetAuthenticationInfo()中进行，返回的是AuthorizationInfo对象和AuthenticationInfo对象。这两个分别表示着授权信息和验证信息。在我的代码中，对于前者，我通过setRoles()函数向其中添加了和当前用户有关的角色信息，并且通过setStringPermissions()函数向其中添加了和当前用户有关的权限信息，然后返回。而对于后者，在构建authenticationInfo的时候，使用了四个参数，分别是登录者发送的用户名和密码，还有随机生成的盐值，以及这个realm的类名。再到用户验证，那么要匹配登录者发送的信息和数据库中对应的信息，就会有个验证和匹配的地方，这个匹配的借口我在RetryLimitHashedCredentialsMatcher类中实现，它继承了HashedCredentialsMatcher这个类，这里的匹配和验证本质上是交给父类去做的，而我增加的内容，则是从缓存中取出现在登录的这个用户名，它的连续错误登录次数，若是过多，则会抛出ExcessiveAttemptsException。这些以上的内容，都会在配置文件spring-shiro.xml中用到大概的顺序级和关系大概如下：未命名文件-2然后再到我的登录入口，我经常会用到Subject对象，这个对象存有用户相关的各种信息，例如用户名，sessionID等，登录和登出操作也是以它为入口进行的。而Subject对象为什么能存储呢，通过观看源码可以发现，它从SecurityUtils中返回，而这又是从ThreadContext.getSubject()中获取的，再接着看，创建Subject的时候，是将其和ThreadContext绑定的，相当于与线程绑定了。然后演示一下首先进行登录，试试输错的情况：屏幕快照 2019-06-23 23.47.36.png然后成功登录，并在返回信息中加入用户相关的信息，如昵称，性别，头像url等：屏幕快照 2019-06-23 23.54.47.png然后可以看看cookie中的sid：屏幕快照 2019-06-23 23.48.44.png然后进行信息的请求，这个API是需要用户登录的，因此登录了的用户可以成功通过：屏幕快照 2019-06-23 23.52.57.png然后删除sid，此刻已经相当于未登录状态，即一个新的会话，然后再次请求刚刚的那个API：屏幕快照 2019-06-23 23.53.18.png出现错误了，即表明shiro框架的验证和匹配是成功的。再试试看持久登录，rememberMe的选项：屏幕快照 2019-06-24 00.17.33.png可以看到，cookie中多了rememberMe的key-value，这个就是用来存储在客户端，能够保持持久登录的字段和密文。试试重启服务器，并且删去sid：屏幕快照 2019-06-24 00.20.17.png依然是成功获取了，这表明持久化登录的功能是成功了的四、代码详情和解析我选择的是shiro框架来实现用户登录和验证的功能，正好附带了权限验证的功能，能够在后续实现用户的锁定，设置超级管理员等等，可以方便App的功能升级。而权限验证也方便了Api的开发。首先需要改变表的结构，当然，可以结合之前的表，将之前的表改为userinfo，对应的mapper，service，dao和controller也改为userinfoxxx，然后在数据库中添加如下表格：create table sys_users ( id bigint auto_increment comment '编号', username varchar(100) comment '用户名', password varchar(100…

登录系统的表设计和Shiro框架
type: item
date: 2019-06-21T03:26:00+08:00
description: 一、目录用户表的设计重定向和转发shiro框架初探二、实验结果和学习内容用户表的设计一开始，用于测试的时候，用户表就是简单的username加password两个字段，而为了后续的功能实现，我需要增加一个userID，并且尽量使得不能存在溢出的可能（当作亿级用户量来处理）。并且如果应用支持第三方登录，或者只使用第三方登录，那么第三方的 User ID 有可能是 64 位的。虽然自己的应用不一定能增长到超过 32 位，但是第三方很可能是 64 位的。然后还要增加用户状态 status 判断用户是否被封禁，注册时间和注册IP地址、上次登录时间和IP地址备查（并衍生出登录记录表，用来判断是否异地登录等，在此不表），用户角色/权限 role （又衍生出用户角色权限关系），业务也需要个人的个人信息如真实姓名、地址等也要往上添加，现在形成了一个很完整的用户关系表。在这之后，微博开放了第三方网站登录，用微博帐号就能登录。那就要增加个微博用户登录表，并且和本来的用户表关联，则这个微博用户信息表如下：id 自增ID user_id 关联本站用户ID uid 微博唯一ID access_token access_expire不仅如此，算上QQ和微信，一下子要接入好多家第三方登录了，只能就着“微博用户信息表”继续加类型加判断，如果是每个第三方登录都新建一个表，肯定会很冗杂。而作为iOS应用，必须要支持手机号登录。所以表结构如下：用户表 id username email phone … 用户第三方登录表 id user_id app_type app_user_id access_token进行改进无论username+password，还是phone+password，都是一种用户信息+密码的验证形式；再来理解第三方登录，其实它也是用户信息+密码的形式，用户信息即第三方系统中的ID（第三方登录一定会给一个在他们系统中的唯一标识），密码即access_token，只不过是一种有使用时效定期修改的密码。所以我们把它抽象出了用户基础信息表加上用户授权信息表的形式。用户基础信息表 users id nickname avatar 用户授权信息表 user_auths id user_id identity_type 登录类型（手机号 邮箱 用户名）或第三方应用名称（微信 微博等） identifier 标识（手机号 邮箱 用户名或第三方应用的唯一标识） credential 密码凭证（站内的保存密码，站外的不保存或保存token）这个系统最大的特色就是，用户信息表不保存任何密码，不保存任何登录信息（如用户名、手机号、邮箱），只留有昵称、头像等基础信息。所有和授权相关（且基本前端展示无关的），都放在用户信息授权表，用户信息表和用户授权表是一对多的关系。例如：usersidnicknameavatar1慕容雪村http://…/avatar.jpg2魔力鸟http://…/avatar2.jpg3科比http://…/avatar3.jpguser_authsiduser_ididentity_typeidentifiercredential11email123@example.compassword_hash(密码)21phone13888888888password_hash(密码)31weibo微博UID微博access_token42usernamemoliniaopassword_hash(密码)53weixin微信UserName微信token具体处理如下，当用户发来邮箱/用户名/手机号和密码请求登录的时候，依然是先判断类型，以某用户使用了手机号登录为例，使用 SELECT * FROM user_auths WHERE type=’phone’ and identifier=’手机号’ 查找条目，如有，取出并判断password_hash(密码)是否和该条目的credential相符，相符则通过验证，随后通过user_id获取用户信息。如果使用第三方登录，则只要判断 SELECT * FROM user_auths WHERE type=’weixin’ and identifier=’微信UserName’ ，如果有记录，则直接登录成功，使用新的token更新原token。假设与微信服务器通信不被劫持的情况下无需判断凭证问题。--重定向和转发然后由于功能实现需要用到转发或者重定向的功能，因此了解了一下：1.使用HttpServletResponse 重定向到另一个视图 @RequestMapping("/resp") public void handleRequest(HttpServletRequest req, HttpServletRe…

实现通过POST请求进行注册
type: item
date: 2019-06-21T01:58:00+08:00
description: 一、目录实现通过POST请求进行账号注册通过POST发送账号返回密码了解各种注解作用和POST详情二、实验结果和学习内容首先因为要解析JSON数据以及返回JSON数据，因此我在选择依赖包的时候，在JSON-lib，Jackson和fastjson中选择了Jackson，那么添加依赖，这时候发现之前用的阿里云的maven仓库好像没有最新的版本，于是在下面的网站中查找，换了一个库。AliRepo添加依赖： <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.9.8</version> </dependency> 然后因为我要实现注册用户，以及返回用户密码，那么就要在Dao层和Service层进行修改，然后发现Dao层的两个方法分别是通过用户获取整个用户对象，以及直接向数据库添加用户。然后在Service层增加函数 public String findUser(String username) { User user = userDao.findByUsername(username); if(user != null){ return user.getPassword(); } return "Not Found!"; } 这样就能返回用户密码了。然后在Controller中实现我所要实现的功能。首先是用户注册，这里我返回的一组测试数据。 @ResponseBody @RequestMapping(value = "/trypost2", method = RequestMethod.POST) public List<User> trypost2(@RequestBody User user){ System.out.println(user.getId()+" "+user.getPassword()+" "+user.getPassword()); List<User> list = new ArrayList<User>(); User user1 = new User(); user1.setUsername("00001111"); user1.setPassword("tttttttt"); User user2 = new User(); user2.setUsername("22221111"); user2.setPassword("pppppppp"); list.add(user1); list.add(user2); userServivce.Regist(user); return list; }其中注解@ResponseBody的作用是将controller的方法返回的对象通过适当的转换器转换为指定的格式之后，写入到response对象的body区，通常用来返回JSON数据或者是XML数据，而需要注意的是，在使用此注解之后不会再走试图处理器，而是直接将数据写入到输入流中，他的效果等同于通过response对象输出指定格式的数据。 @RequestMapping("/login") @ResponseBody public User login(User user){ return user; } //User字段：userName pwd //那么在前台接收到的数据为：'{"userName":"xxx","pwd":"xxx"}' //效果等同于如下代码： @RequestMapping("/login") public void login(User user, HttpServletResponse response){ response.getWriter.write(JSONObject.fromObject(user).toString()); } 然后可以进行测试了，因为直接使用curl比较麻烦，因此我下载了一个Postman，用于测试API。效果如下：这是请求：然后得到的结果：说明返回成功了，然后看看服务器的输出：可以看的出成果连接数据库并成功插入了数据，再看看数据库的表的变化：pic7.png产生了变化了说明这个API是成功了的然后是第二个API： @ResponseBody @RequestMapping(value = "/trypost", method = RequestMethod.POST) public List<String> trypost(@RequestParam(value = "username") String username){ List<String> list = new Arr…

SSM框架入门
type: item
date: 2019-06-21T01:54:00+08:00
description: 一、目录通过SSM框架实现用户登录注册的功能部署到tomcat服务器上二、实验结果和学习内容首先前期准备当然是下载并安装好MySQL，Tomcat8，还有Maven了首先配置好maven之后，通过maven来构建项目：然后构建好项目目录：首先是依赖文件pom.xml，即包含了要用到的包，会下载到maven的本地仓库中：<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>sysu.wangjing</groupId> <artifactId>mySSM</artifactId> <version>1.0-SNAPSHOT</version> <packaging>war</packaging> <name>mySSM Maven Webapp</name> <url>http://www.example.com</url> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <maven.compiler.source>1.7</maven.compiler.source> <maven.compiler.target>1.7</maven.compiler.target> </properties> <dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.11</version> <scope>test</scope> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>1.7.7</version> </dependency> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-core</artifactId> <version>1.1.1</version> </dependency> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> <version>1.1.1</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.30</version> <scope>runtime</scope> </dependency> <dependency> <groupId>c3p0</groupId> <artifactId>c3p0</artifactId> <version>0.9.1.2</version> </dependency> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.2.8</version> </dependency> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis-spring</artifactId> <version>1.2.2</version> </dependency> <dependency> <groupId>taglibs</groupId> <artifactId>standard</artifactId> <version>1.1.2</version> </dependency> <dependency> <groupId>jstl</groupId> <artifactId>jstl</artif…

Kafka消息队列和用户登录方案设计
type: item
date: 2019-06-21T01:50:00+08:00
description: 一、目录kafka详解用户登录方案二、实验结果和学习内容Kafka 详解Kafka是一种分布式的，基于发布/订阅的消息系统。主要设计目标如下：以时间复杂度为O(1)的方式提供消息持久化能力，即使对TB级以上数据也能保证常数时间的访问性能高吞吐率。即使在非常廉价的商用机器上也能做到单机支持每秒100K条消息的传输支持Kafka Server间的消息分区，及分布式消费，同时保证每个partition内的消息顺序传输同时支持离线数据处理和实时数据处理而我们的项目目标是完成一款Feed流App，显然消息队列系统是非常重要的。送达保证 消息队列提供的冗余机制保证了消息能被实际的处理，只要一个进程读取了该队列即可。在此基础上，部分消息系统提供了一个”只送达一次”保证。无论有多少进程在从队列中领取数据，每一个消息只能被处理一次。这之所以成为可能，是因为获取一个消息只是”预定”了这个消息，暂时把它移出了队列。除非客户端明确的表示已经处理完了这个消息，否则这个消息会被放回队列中去，在一段可配置的时间之后可再次被处理。顺序保证 在大多使用场景下，数据处理的顺序都很重要。消息队列本来就是排序的，并且能保证数据会按照特定的顺序来处理。部分消息系统保证消息通过FIFO（先进先出）的顺序来处理，因此消息在队列中的位置就是从队列中检索他们的位置。缓冲 在任何重要的系统中，都会有需要不同的处理时间的元素。例如,加载一张图片比应用过滤器花费更少的时间。消息队列通过一个缓冲层来帮助任务最高效率的执行–写入队列的处理会尽可能的快速，而不受从队列读的预备处理的约束。该缓冲有助于控制和优化数据流经过系统的速度。结构Broker Kafka集群包含一个或多个服务器，这种服务器被称为brokerTopic 每条发布到Kafka集群的消息都有一个类别，这个类别被称为topic。（物理上不同topic的消息分开存储，逻辑上一个topic的消息虽然保存于一个或多个broker上但用户只需指定消息的topic即可生产或消费数据而不必关心数据存于何处）Partition parition是物理上的概念，每个topic包含一个或多个partition，创建topic时可指定parition数量。每个partition对应于一个文件夹，该文件夹下存储该partition的数据和索引文件Producer 负责发布消息到Kafka brokerConsumer 消费消息。每个consumer属于一个特定的consumer group（可为每个consumer指定group name，若不指定group name则属于默认的group）。使用consumer high level API时，同一topic的一条消息只能被同一个consumer group内的一个consumer消费，但多个consumer group可同时消费这一消息。kafka的四个核心Api应用程序使用producer API发布消息到1个或多个topic中。应用程序使用consumer API来订阅一个或多个topic,并处理产生的消息。应用程序使用streams API充当一个流处理器,从1个或多个topic消费输入流,并产生一个输出流到1个或多个topic,有效地将输入流转换到输出流。connector API允许构建或运行可重复使用的生产者或消费者,将topic链接到现有的应用程序或数据系统。kafka配置说明ProducerProperties props = new Properties(); props.put("bootstrap.servers", "master:9092,slave1:9092,slave2:9092"); props.put("acks", "all"); props.put("retries", 0); props.put("batch.size", 16384); props.put("key.serializer", StringSerializer.class.getName()); props.put("value.serializer", StringSerializer.class.getName()); KafkaProducer<String, String> producer = new KafkaProducer<String, String>(props);其中：bootstrap.servers： kafka的地址。acks:消息的确认机制，默认值是0；acks=0：如果设置为0，生产者不会等待kafka的响应。acks=1：这个配置意味着kafka会把这条消息写到本地日志文件中，但是不会等待集群中其他机器的成功响应。acks=all：这个配置意味着lea…

服务端技术架构和Nginx详解
type: item
date: 2019-06-21T01:25:00+08:00
description: 一、目录服务端常用技术栈服务端开发常用组件信息流服务架构设计Nginx详解二、学习内容数据存储用户资料商品文章图片视频传输消息传递转账上传下载计算数据计算地图导航搜索信息流推荐提供算力语音合成语音实时转写图像内容识别技术栈主要包括基础设施，支撑系统，接入层，逻辑层和存储层，这是C/S架构模式其中基础设施主要是服务运行的环境，大多和硬件，底层系统有关，比较泛化。而支撑系统，主要目的是支撑服务端连续运行，监控错误和漏洞，并收集服务端产生的信息，达到方便维护或者自动维护的效果CI/CDCI/CD 是一种通过在应用开发阶段引入自动化来频繁向客户交付应用的方法。CI/CD 的核心概念是持续集成、持续交付和持续部署。作为一个面向开发和运营团队的解决方案，CI/CD 主要针对在集成新代码时所引发的问题（亦称：“集成地狱”）。具体而言，CI/CD 在整个应用生命周期内（从集成和测试阶段，到交付和部署）引入了持续自动化和持续监控。这些关联的事务通常被统称为“CI/CD 管道”，由开发和运维团队以敏捷方式协同支持。接入层功能：维护与客户端之间的网络连接，管理客户端的网络状态。接收客户端请求，将请求转发到业务层，转发业务层发给客户端的数据。就近接入，负载均衡，优化网络体验。反向代理反向代理是代理服务器的一种。服务器根据客户端的请求，从其关系的一组或多组后端服务器（如Web服务器）上获取资源，然后再将这些资源返回给客户端，客户端只会得知反向代理的IP地址，而不知道在代理服务器后面的服务器簇的存在。而 Nginx 就是性能非常好的反向代理服务器，它可以用来做负载均衡。负载均衡负载均衡（Load Balance）其意思就是分摊到多个操作单元上进行执行，例如Web服务器、FTP服务器、企业关键应用服务器和其它关键任务服务器等，从而共同完成工作任务。负载均衡是高可用网络基础架构的的一个关键组成部分，有了负载均衡，我们通常可以将我们的应用服务器部署多台，然后通过负载均衡将用户的请求分发到不同的服务器用来提高网站、应用、数据库或其他服务的性能以及可靠性。负载均衡算法 ：轮询：为第一个请求选择健康池中的第一个后端服务器，然后按顺序往后依次选择，直到最后一个，然后循环。最小连接：优先选择连接数最少，也就是压力最小的后端服务器，在会话较长的情况下可以考虑采取这种方式。散列：根据请求源的 IP 的散列（hash）来选择要转发的服务器。这种方式可以一定程度上保证特定用户能连接到相同的服务器。如果你的应用需要处理状态而要求用户能连接到和之前相同的服务器，可以考虑采取这种方式。流量清洗对进入的数据流量进行实时监控，及时发现包括DOS攻击在内的异常流量。在不影响正常业务的前提下，清洗掉异常流量。逻辑层作用是根据业务逻辑，处理用户请求，得到结果。设计模式有单体式服务和微服务。单体式模式所有的业务逻辑都集中在一个代码仓库，一个进程里优点是：简单，适合协作人数少，功能较少，且维护和更新较方便缺点是：难以支持高复杂度的系统微服务业务逻辑按功能或类型作划分，分别部署多个服务，使用网络通信交换数据优点是：多人团队协作成本更低，功能复用，系统解耦，故障隔离缺点是：影响性能，系统复杂性大增，对业务流程设计提出了更高的要求，且对网络，基础框架的要求更高RPCRPC（Remote Procedure Call）—远程过程调用，它是一种通过网络从远程计算机程序上请求服务，而不需要了解底层网络技术的协议。RPC协议假定某些传输协议的存在，如TCP或UDP，为通信程序之间携带信息数据。在OSI网络通信模型中，RPC跨越了传输层和应用层。RPC使得开发包括网络分布式多程序在内的应用程序更加容易。常用的RPC框架：gRPCbRPCThriftRPC要解决的问题是：解决分布式系统中，服务之间的调用问题。远程调用时，要能够像本地调用一样方便，让调用者感知不到远程调用的逻辑。实际情况下，RPC很少用到http协议来进行数据传输。而不管使用何种协议进行数据传输，一个完整的RPC过程，都可以用下面这张图来描述：通信协议HTTP：超文本传输协议，数据交换协议的一种万维网数据通信的基础基于 TCP，协议简单HTTPS：TLS 加持的 HTTP提供网站服务器身份认证保护数据交换的隐私以及完整性校验存储层存储层就是DBMS,负责管理对数据库数据的读写和转发。DBMS必须能迅速执行大量数据的更新和检索。现在的主流是关系数据库管理系统(RDBMS)。因此,一般从逻辑层传送到存储层的要求大都使用SQL语言。消息队列“消息队列”是在消息的传输过程中保存消息的容器，其中的“消息”指的是在两台计算机间传送的数据单位。消息可以非常简单，例如只包含文本字符串；也可以更复杂，可能包含嵌入对象。消息被发送到队列中。消息队列管理器在将消息从它的源…