Java八股

来嘛,背嘛兄弟, 持续更新

Java基础

ArrayList和LinkedList的数据结构与区别

ArrayList内部是一个Object[], 由于内部采用数组实现,所以优点是可以根据下标可以快速访问元素,缺点是在增加删除时,需要移动整个数组,效率较低,并且数组需要占用连续内存。
LinkedList是一个双向链表,因此优点是比较容易添加删除节点,缺点是搜索访问时需要指针从头查找,效率较低,链表不需要连续内存。
ArrayList和LinkedList都是List接口的实现类,他们都是线程不安全的。

ArrayList扩容机制

ArrayList的add方法调用时,会先调用ensureCapacityInternal进行扩容。

HashMap数据结构

首先HashMap在Java7和Java8里实现有一些不一样,java8在7的基础上进行了优化
在Java7中,HashMap内部是一个Node节点数组,其中Node是一个单向链表节点,只指向下一个元素。所以可以说,HashMap内部是一个数组,数组的每个元素是一个链表,即数组+链表。这样的构造是为了解决put时的哈希碰撞问题。

HashMap put过程, 扩容过程,怎么判断元素是否重复?链表转红黑树的原因

HashMap在put一对Key/Value时,有以下步骤:

  • HashMap使用自己实现的Hash方法,计算得到Key的hash值
  • 检查内部的Node节点数组(可称作哈希槽)是否为空,如果为空,调用resize方法进行初始化(resize也负责扩容)
  • 扩容的逻辑:如果哈希槽为空,先创建一个默认初始长度为16的数组;如果哈希槽已经初始化,检查是否达到扩容阈值,默认为0.75*当前长度,如果达到了,扩容为当前2倍
  • 接下来继续走put流程,通过(n-1)&hash 计算出元素该位于哈希槽的哪个位置,其中n为当前哈希槽长度,hash为HashMap计算出的key的hash值, 如果该位置上没有东西,直接创建个Node放在这里
  • 如果发现此处已有Node,开始判断是否重复,如果此处的Node的hash和key值,和要put的key的hash值和本身的值均相同,那么就认为是重复元素,直接返回。
  • 如果发现此处已有Node, 但是并非重复,如果是TreeNode, 即链表已转化为红黑树,此时往红黑树中插入元素
  • 如果发现此处已有Node, 但是并非重复,如果不是TreeNode, 即此时还是链表,先将元素插入到链表尾,再调用treeifBin方法,判断如果哈希槽长度小于64,先尝试resize扩大哈希槽;如果哈希槽长度小于64,切链表长度超过8,将链表转化为红黑树。
  • 最后再判断,如果当前Map容量超过75%, 即0.75*当前容量, 再resize一次

链表转红黑树,是为了提高查找效率。
试想一下get过程,HashMap计算key的hash值,再定位到hash槽上,此时hash槽上是一个有多个节点的链表,此时HashMap会从链表中查找hash和key值都相同的节点,因为是链表中查询,时间复杂度为 O(n), 而红黑树的查询时间复杂度为O(logN)

3.红黑树数据结构?左旋和右旋过程?
B tree, B+tree,
todo

4.HashMap是否线程安全?线程安全的Map?怎么实现的?
HashMap不是线程安全的Map。线程安全的Map是ConcurrentHashMap。
在java1.7中,ConcurrentHashMap的内部分为多个Segment,每个Segment中是类似HashMap的结构,当对ConcurrentHashMap进行操作时,使用RetrantLock对操作的Segment加锁,可以理解为对一段数据进行加锁。在Java1.8中,ConcurrentHashMap使用的是HashMap一样的数组+链表/红黑树结构,对链表/红黑树的节点使用sychronized,从而达到线程安全。

5.CAS原理?怎么解决ABA问题?
CAS指Compare and Swap , 比较后再交换值。
其中比较的对象有3个,可以用一句话来说,就是 把x的y值替换成z值
其中x是要替换的目标, y是当前值, z是要替换成的值。
CAS可以实现乐观锁,当线程要去修改某个目标时,先做一次CAS,如果满足条件,就操作成功,否则开始自旋直到满足条件,这样线程就不会挂起。
按照上述CAS定义,就会出现如下情况:
线程a需要将当前的1增加到2,而线程b抢先将1改成了3,线程c又将3改回到1,此时线程a进入,发现当前值仍然是1,便觉得满足条件,而实际上真实数据已经绕了一大圈回来了。
对一个线程来说,不同通过现有的3个比较对象判断出是否发生过ABA时,便要引入新的变量来辅助判断,通常来说,会引入一个简单的标志位,例如changed, 来标志是否发生过更改,或者假如版本号,来标识发生修改的次数。

AQS?

见另一篇文章** **https://jarryzhou.space/article/tkq7gc

线程池原理

线程池是一种针对线程的池化技术。线程池提前准备好一些线程,另其能重复使用,以减少线程创建和销毁的开销,并且可以通过参数控制线程占用的资源,自如线程数,等待时间等。
线程池有以下几个核心参数:
核心线程数 - 核心线程的数量,核心线程空闲时不会销毁。
最大线程数 - 线程池最大可容纳线程数
keepAliveTime - 除了核心线程外其它线程空闲时最大存活时间
blockingQueue - 任务等待队列
threadFactory - 线程工厂
rejectedExecutionHandler - 拒绝处理器

线程池有哪几种拒绝策略

线程池有4种拒绝策略

1
2
3
4
AbortPolicy abortPolicy = new ThreadPoolExecutor.AbortPolicy();
DiscardPolicy discardPolicy = new ThreadPoolExecutor.DiscardPolicy();
DiscardOldestPolicy discardOldestPolicy = new ThreadPoolExecutor.DiscardOldestPolicy();
CallerRunsPolicy callerRunsPolicy = new ThreadPoolExecutor.CallerRunsPolicy();

线程池的线程用完后如何回收

谈谈对synchronized的理解,底层如何实现

sychronized是java中用于线程同步的关键字,可以作用于类,对象,方法,代码块,字段上。被Sychronized修饰的部分表示只允许单个线程进入,它是一种悲观锁。
java堆中的对象内存模型主要分为3个部分:

  - 对象头 - 又主要分为两个部分,Mark Word 标识极端,用于存放对象运行时的一些标记 和 Class Pointer 类型指针,指向对象类型元数据
  - 实例属性数据 - 对象实例的属性数据
  - 对齐填充数据 - 为了对齐到8字节的整数倍而填充的数据

其中对象上的锁的信息,存放在对象头的MarkWord当中,锁有4个级别,从低到高分别为无锁,偏向锁,轻量锁,重量锁。

锁升级的过程

锁升级,指的是JVM对synchronized关键字的优化,另其在不同情况下,使用不同的策略,以达到高效的目的。
锁升级是一个从无锁定到重量锁的不可逆过程。
当遇到synchronized关键字时,线程尝试获取对象锁,走了以下流程:
当对象上没有锁,就直接获取锁,并将线程id设置到对象头中,使之成为偏向锁,偏向此线程,并且不会显式释放,若下次同样线程到来,就没有锁定和释放锁的过程,节约了资源。这样做是因为根据经验,在实际场景中,锁大多数情况下都只被一个线程获得。
此时,若另一个线程到来,遇到偏向锁,若偏向的线程已死,或者当前对象不处于被锁定状态,则对象重置到无锁状态,然后再成为新线程的偏向锁。若偏向的线程还继续持有锁,那么此时自动升级到轻量锁。
轻量级锁就是常见的自旋锁,线程通过CAS,尝试获得对象锁,当超过了自旋限制仍未成功获得锁时,或者又来了新的竞争者,则升级为重量级锁,即将除了获得锁的线程以外的所有线程阻塞,防止cpu浪费。

讲一下volatile?讲讲几个内存屏障,嗅探机制

volatile关键字可修饰字段,它有2个功能,一是防止代码编译时的指令重排,二是强制数据刷入本地内存。
指令重排指的是编译字节码时,在不改变程序输出结果的情况下,对代码顺序进行的优化调整。
强制数据从内存中存取
todo

谈一下JMM

一般来说只聊Hotspot的实现。
按照线程之间的可见行,可以分为线程共享的区域,和线程私有的区域
线程共享的区域有:
堆区 - 这是所有线程共享的内存区域,用来存放所有对象实例
方法区 - 存放加载的类信息,常量
线程私有的区域:
栈 - 栈中又分为本地方法栈,jvm栈,
todo

什么是静态代理和动态代理

动态代理有几种方式?

2种,

  1. JDK提供的InvocationHandler接口

实现InvocationHandler接口的invoke方法

  1. cglib

实现MethodInterceptor的intercept方法

Spring

讲一下spring的IOC和AOP?

IOC的意思是控制反转,是抽象思想的体现,指在编码中依赖抽象层而不是依赖具体实现,进而降低了代码之间的耦合,增加了灵活性,实现了多态。
AOP
TODO

Spring AOP底层实现

aop通过2种方式来实现

  1. 动态代理

spring支持jdk动态代理,即invocationHandler接口,以及cglib两种方式。默认的策略是,如果目标类是接口,用jdk代理,如果是对象,使用cglib, 因为接口好继承接口,而已有对象又不方便去修改它,所以分别采用两种策略应对不同情况。
动态代理做aop就好理解了,代理后在前后做一些事情。

  1. 静态织入

Spring中有哪些设计模式

工厂-
代理-
单例 - 这tm也算。。
观察者 -

Spring BeanFactory和FactoryBean的区别
BeanFactory是spring容器核心接口,todo

bean的作用域

singleton - 在一个applicationcontext下的单例
prototype - 原型模式,每次从容器中拿,都是新的
globalsession - 每个全局session中是一个,这个只在portlet应用中的概念,servlet应用中与session没有区别
sesstion - 每个http session 一个
request - 每个http request一个

10.bean对象的生命周期?
11.spring的事务传播机制,通过AOP的整个实现过程?
Spring事务具有数据库事务同样的acid特性,此外Spring定义了在方法调用当中事务是如何传递的,即传播机制。
Spring的事务传播机制一共有以下几种
Required
如果当前有事务,加入当前事务,如果没有新建一个

REQUIRES_NEW
无论如何,新建事务,如果原来有事务,将原有事务挂起,执行完本事务后继续

MANDATORY
在原有事务中执行,如果原来没有事务,抛出异常

NEVER
必须在没有事务的情况下执行,否则抛出异常

NOT_SUPPORTED
不开启事务

SUPPORTS
有就用,没有算了

todo

Spring是如何解决循环依赖问题的

Spring靠内设的三级缓存机制解决循环依赖问题
一级缓存singletonObjects:用于保存实例化、注入、初始化完成的bean实例
二级缓存earlySingletonObjects:用于保存实例化完成的bean实例
三级缓存singletonFactories:用于保存bean创建工厂,以便于后面扩展有机会创建代理对象。
从三级到一级,里面的bean是越来越完善的,而查找bean是从一级找到三级
那么看一个互相依赖的例子

1
2
3
4
5
6
7
8
9
10
11
@Service
public class TestService1 {
@Autowired
private TestService2 testService2;
}

@Service
public class TestService2 {
@Autowired
private TestService1 testService1;
}

SpringBoot自动装配原理

数据库相关

mysql索引结构?怎么优化sql?

mysql采用的是B+tree的索引结构,todo

mysql隔离级别?

在说Mysql事务隔离级别之前,先说一下不同隔离级别下可能出现的问题:
脏读 - 读到了其它事务未提交的数据
不可重复读 - 同事务下,2次读取到的数据不一致
幻读 - 同事务下,第一次读到了数据,再读没有了,反之亦可,就像产生了幻觉

mysql有以下事务隔离级别:
read_uncommited - 未提交读,可以读到其它事务中未提交的数据,此时可能出现脏读,不可重复读,幻读
read_commited - 提交读,提交了才能读到,因此避免了脏读,但是仍可能出现不可重复读和幻读
reapeatable_read - 可重复读,这个是mysql innodb默认的事务隔离级别,避免了脏读,不可重复读,但仍然可能出现幻读
serializable - 串行化,强制事务串行执行,这样就避免了所有问题,但是效率就低了

Mvcc?

MVCC全称Multi-Version Concurrency Control,即多版本并发控制, 是一种并发控制的方法,在数据库管理系统中,实现对数据库的并发访问,在编程语言中实现事务内存。
MVCC在MySQL中的实现是为了在有读写冲突的情况下,实现非阻塞并发读,从而提高性能。

在Mysql innodb情况下,有当前读,和快照读的概念。

当前读,指读取最新的数据,并加行级锁防止其它事务的修改,例如select lock in share mode(共享锁), select for update ; update, insert ,delete(排他锁)

快照读,在串行之前的隔离级别的普通select语句使用的是快照读,它不向记录加锁,因此是非阻塞的,但也因此在并发读情况下可能读到的不是最新记录

mvcc在mysql的具体实现,依赖行记录的3个隐藏字段,undo log ,和Read View
3个隐藏字段:
DB_TRX_ID 最后操作本行记录的事务id
DB_ROLL_PTR 回滚指针,指向上个版本的指针
DB_ROW_ID 隐含的自增ID,row id嘛
undo log:
insert时会产生undo log,用于回滚, 事务提交后丢弃
update/delete时也产生undo log, 也用于回滚,同时用于快照读
todo
13.用过哪些设计模式?
14.自己实现ribbon策略,比如abc三个的权重分别是532你要怎么做,轮询算法怎么实现
15.如果我们不用feign怎么实现通过注解就能调用对应的接口
16.kafka分区的好处,配置等,
17.两个list一个存储id,一个id和name,id相同就把name放入另外一个list,有什么比较高效的方法

18.多线程的拒绝策略和状态有哪些
19.多线程执行任务怎么让他们全部执行完任务后再执行别的任务,自己做怎么实现,多线程中其中一个线程异常如何让所有线程都终止执行
20.springbootapplication中的比较重要的注解有哪些
21.spring.factories中的类加载是怎么实现的
22.refresh主要做了些什么
23.jdk自动支持排序的集合?
25.单向链表做查询效率不高你会怎么优化数据结构
单链表查询要从头开始搜索,时间复杂度O(n).
在数据量大的时候,第一参见HashMap, 将链表转换为红黑树,时间复杂度变为O(logn).
或者做跳跃表

线程池中线程执行完了一个任务接下来是做什么,是等待还是被收回,如果是等待,那么判断的依据是啥,如果是被回收,那么是怎么被回收的。

28.线程池主线程如何捕获线程异常
29.线程池有哪些阻塞队列,他们有哪些优缺点
30.设计一个接口在任一时间轴不允许调用请求超过60次如何设计—-滑动窗口算法
31.用redis命令reset你会怎么设计
32.threadlocal有什么作用,使用会有什么需要注意的地方
33.一个数组,里面存了重复的数据,找到重复最多的;
34.找到一个字符串中最大的对称字符串
35.找到1亿个数据中第5千万大的
36.redis集群/哨兵,区别

分布式/微服务

CAP / BASE理论

分布式事务

分布式缓存

分布式id

1、关于微服务的相关问题 1-2
a、突发的性能问题
b、性能问题
b1、当出现突发的并发时,怎么限流 拒绝服务
nginx / Apache / F5 / Squid 限流
网关限流 spring-gateway spring
算法:
漏桶算法:
限流框架:guava、hystrix、sentinel滑动窗口、
令牌桶算法
突发流量
滑动窗口
redis限流 计算器

2、消息队列相关问题 rabbitmq kafka
a、突发系统断电、正在处理的队列该怎么办
需要消息持久化
正在处理的队列,应该怎么办?
1、有若干条消息正在被处理 需要实现事务,没有事务会出现脏数据
如果有分布式事务 会回滚,如果没有分布式事务 只是回滚当前事务
2、分布式事务的框架
两阶段提交
3、消息并没有被处理
消息队列必须做持久化
4、处理完的消息、处理过程中的消息、没有被处理的消息
获取消息后,先把消息存到数据库、
处理时,表示为处理中
处理完成,标识为处理完成
5、给服务机房配上UBS
7、大并发的消息队列处理
消息队列阻塞
原因:写比度更快、
会造成 消息的丢失
避免消息队列阻塞
读的线程 拿到消息后放到数据库(mysql、redis) ,然后用分布式任务处理
务必要保证读的速度要大于写的速度
b、rabbitmq消息的丢失
断电、网络原因、消息读异常
rabbitmq 消息丢失问题的解决方案?
rabbitmq消费者
autoact 设置为true 自动回复消息
rabbitmq 生产者
发送端 引入事务 确保提交成功
设置持久化
配置集群
d、消息重发消费的问题
幂等 消息的主键的幂等
分布式锁 悲观锁 乐观锁
kafka
kafka的消息会不会丢失?
任何一种消息队列都有可能会发送消息丢失的情况
kafka 通过设计 保证了消息不被丢失
发送消息的方式是send方法,没有callbak 就是发生丢失 如果使用callbak方式 不会被丢失

      cousumer 配置参数 解决消息丢失的问题
         重试没有回复
         重试导致消费者 重复消费的情况

      kafka的吞吐量会大于其他的消息队列
       kafka 的性能高的原因 : NIO 非阻塞IO
       磁盘顺序写
       kafka 零拷贝
       分区和分段
       批量发送
       数据压缩

3、缓存相关问题 redis
redis缓存 雪崩 击穿 穿透
缓存穿透
key不存在
redis key过期
redis数据加载过程中
解决办法
布隆过滤器
拒绝请求
缓冲等待 互斥锁

     缓存雪崩
        缓存没有热加载 冷启动
        缓存预热 先用工具或脚本把缓存数据跑一遍
        请求排队
    redis使用的时需要注意事项:
       1、内存设置
          数据过大 导致存在了磁盘上而不是内存中
          参数调优
       2、持久化方式
          配置方式
       3、集群应用
           主从模式、哨兵模式、Cluster模式
       4、缓存失效的机制
    高性能的原因
    如何使用redis zookeeper 实现分布式锁
    zookeeper 是如何保证事务的顺序一致性
      全局递增Id
    

4、性能调优
a、秒杀活动 架构设计
1、页面信息静态化(尽量确保不变的信息静态化)
2、图片 、js文件、css文件 地址分别 一级域名化
3、缓存数据
静态文件 缓存到浏览器端 http头中设计
启用CDN
服务端 静态页面缓存
动态内容缓存 redis缓存、本地缓存
活动规则缓存 redis缓存、本地缓存
库存、价格 redis缓存、本地缓存
数据库的缓存 数据库本身的缓存
秒杀 尽量避免让请求访问到数据库
在数据中避免使用事务 尽量不要使用外键、不要让表太分散 ToC的表 尽量不要太分散 使用json大字符串

      4、服务限流

前端f5 / nginx / 网关 /
fallback

      5、服务降级
   b、生产系统 出现性能瓶颈 从哪些方面定位问题
      1、数据库 sql性能、数据量
      2、缓存
         缓存命中率低 击穿 雪崩
      4、服务器 性能 cpu 内存 IO
      5、代码实现方案问题
      6、前端问题
         cdn 页面静态化 页面缓存

中间件

RabbitMQ
Kafka

简单算法

二叉树中序遍历
二叉树前序遍历
二叉树后序遍历
二叉树广度优先遍历
二分查找
冒泡排序
快速排序

微服

突发并发—如何限流/削峰

如何生成VCARD静态二维码名片

第一步 编辑VCARD格式的名片信息

下面是我的名片的模版,
其中开始的BEGIN:VCARD VERSION:3.0 和结束的END:VCARD不用管
从第三行开始是真正的信息
每行信息从冒号后面开始编辑,改成自己的信息
从第三行开始,分别是
姓名
电话
邮箱
职位
公司名称
地址

1
2
3
4
5
6
7
8
9
BEGIN:VCARD
VERSION:3.0
N;CHARSET=UTF-8:周坚林
TEL;CELL:13693435345
EMAIL:zhoujianlin@pangod.com
TITLE;CHARSET=UTF-8:软件架构师
ORG;CHARSET=UTF-8:盘古纵横集团
ADR;CHARSET=UTF-8:成都市锦江区东御街19号茂业天地A座42
END:VCARD

第二步 生成二维码
百度所有一个在线的二维码生成,通常我们使用草料二维码生成器
image.png

选择文本,把编辑好的信息粘贴进去, 点击生成二维码

image.png

完成! 右边是生成的二维码,是静态永久的,接下来可以通过微信发送给别人用了
image.png

微信扫描之后,可以直接识别信息并保存为联系人
baf4c0bfbe6d16a992dcf5ac578b1c8.jpg

SXK课程推荐ItemCF算法设计

概述

SXK中需要向终端用户进行课程或活动推荐

最基本的推荐算法是协同过滤算法,通常又有2种

  • ItemCF - 商品协同过滤(Collaboration Filter)

即对于被推荐的目标用户,先找出该用户偏好的商品,再对其推荐具有相似特征的商品,俗称“物以类聚”

  • UserCF - 用户协同过滤(Collaboration Filter)

即对于被推荐的目标用户,先找出和其行为相似的其他用户,再向其推荐这些相似行为用户偏好的商品,俗称“人以群分”

协同过滤算法的核心是相似度计算,比如ItemCF是计算商品之间的相似度,UserCF是计算用户之间的相似度

两种协同过滤对比

对比项目 UserCF ItemCF
性能 适用于用户较少的场合,如果用户过多,计算用户相似度矩阵的代价较大 适用于物品数明显小于用户数的场合,如果物品很多,计算物品相似度的代价交大
推荐覆盖率 取决于相似用户偏好领域广度 较为单一,只覆盖相似物品
实时性 用户有新行为,不一定需要推荐结果立即变化 用户有新行为,一定会导致推荐结果的实时变化
冷启动 在新用户对少的物品产生行为后,不能立即对他进行个性化推荐,因为用户相似度是离线计算的
新物品上线后一段时间,一旦有用户对物品产生行为,就可以将新物品推荐给其他用户 新用户只要对一个物品产生行为,就能推荐相关物品给他,但无法在不离线更新物品相似度表的情况下将新物品推荐给用户
推荐理由 很难提供 可以根据用户历史偏好行为归纳推荐理由

由于两种算法各有优劣,单一使用并不是最佳选择,因此在实际落地时,应当考虑混合算法。
例如向每个用户推荐10个物品,先推荐相似物品5个,再从相似偏好用户的其他物品中补充5个。

针对SXK的情况,理论上的正常形态应该是用户数量远远大于课程,因此首先采用itemCF算法,计算课程之间的相似度,并对用户进行推荐

推荐计算流程

基于用户对课程的偏好度的协同过滤推荐算法流程
流程解释

  1. 计算每个课程的用户偏好度

偏好度是用户对于一个课程的各项关联数据的一个相加结果
首先搜集能表达用户与课程之间关系的数据,称为计算因子,包括:

计算因子 权重
单次课程购买 3
课程收藏 2
机构收藏 1
课程评分 1
兴趣标签匹配 1
地理位置 2
后台加权次数 5

计算因子需要乘以权重,权重由系统预先定义。
按照以上计算因子,计算出被推荐用户对每门课程的偏好度,

  1. 偏好度降噪,归一

在计算偏好度的过程中,会出现一些错误值或缺失值,需要把这些异常值去掉,称为降噪。例如,某采购员,所有分类的商品都要大量采购,那么其偏好所有商品,此时其行为不可参考,需要去掉。

归一化的意思是将特征值用统一的标杆对齐。比如同一时间指标,有的用年,有的用秒,需要统一计算单位。

  1. 计算课程之间相似度

计算课程1和课程2之间的欧式距离以表征相似度
欧式距离=偏好度的差的平方的正平方根
假设课程A,课程B评分为, 5, 8
根据n维空间的欧几里得距离公式

那么课程1和课程2之间的相似度为

同上,课程两两计算后,最终可以获得一个对称矩阵如下(数据是乱写的)

课程A 课程B 课程C
课程A 0.3 0.6 0.33
课程B 0.4 0.7 0.22
课程C 0.5 0.4 0.14
  1. 邻居筛选

找出与目标用户最相近的N个课程
有了物品或用户两两之间的相似值,则可以选择每个物品或用户的邻居物品或用户。
如果设定好了邻居个数为X,那么在N-1个item中相似性topX的item为其邻居;如果设定了邻居的最小相似性simy,那么N-1个item中相似性大于simy的为邻居。筛选邻居可以降低矩阵的负责程度和噪音,也可以在最后实现推荐的时候确定推荐物品的个数。

  1. 构建相似矩阵

在步骤计算相似性的最终结果,就是本部分需要的相似矩阵。在某些情况下经过筛选邻居,会将原始的相似性矩阵进行降维,如将矩阵中0只过多的item去掉。
比如在计算时尚女装的推荐时,发现跟钢材工具类物品相似性极低,则在全量的相似矩阵剔除掉钢材工具物品的部分。相当于去掉矩阵中0向量。

  1. 计算推荐结果

接口设计

测试方法

在已知测试数据集合的情况下,验证 均方根误差(RMSE)和预测准确度(MAE)

SXK敏感词过滤功能设计

概述

SXK系统中的运营管理、营销管理、活动发布、课程管理、咨询管理等模块涉及了大量文本、媒体内容。这部分内容在日常运营工作当中,产生了大量的审核工作,传统的人工审核会消耗运营部门大量人力与时间,因此需要以人工审核为基础,构建系统自动审核敏感内容审核功能,以减少人工消耗,提高工作效率。
目前需要审核的内容类型包含文本,图片,视频。结合目前平台开发和运营情况,在第一期先完成文本内容的自动过滤功能。

敏感词过滤流程

流程解释:
现有的审核大致分为2种:

  1. 内容生效前先行审核

如评论,意见等

  1. 先创建待审核的内容副本,审核通过之后,用副本替换正式内容

此类审核是为了保证线上运行时数据正确性
如课程,教师信息等

考虑到以下2点:

  • 不干扰当先流程,不造成较大改动
  • 无论自动审核是否通过,原有的人工审核必须存在,以减少误判,自动审核只为人工审核提供便利

因此,在原本的人工审核部分之前,调用关键词过滤服务,若出现敏感词,写入记录,在原先人工审核处进行提示,此后处理流程保持不变,最终审核交由人工操作

模型设计

  • 敏感词条 - SensitiveText

用于存储敏感词条

  • 敏感词命中历史 - SensitiveContentHistory

自动敏感词过滤命中时的记录,记录下时间,内容提交人,原始内容,敏感内容,审核对象信息
该数据此后可作为评价、监控等的数据依据

数据库设计请参考模型设计

核心接口/代码

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
package io.github.jarryzhou.sensitivecontentfilter.service;

import io.github.jarryzhou.sensitivecontentfilter.dto.SensitiveContentCheckResult;
import io.github.jarryzhou.sensitivecontentfilter.entity.SensitiveContentHitHistory;
import io.github.jarryzhou.sensitivecontentfilter.entity.SensitiveText;
import io.github.jarryzhou.sensitivecontentfilter.repository.SensitiveContentHitHistoryRepository;
import io.github.jarryzhou.sensitivecontentfilter.repository.SensitiveTextRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

/**
* SensitiveContentCheckService
* <p>
* Author: Jarry Zhou
* Date: 2021/9/29
* Description: 敏感内容检测服务
**/
@Service
public class SensitiveContentCheckService {
public static final String SENSITIVE_TEXT_DELIMITER = ",";

@Autowired
private SensitiveTextRepository sensitiveTextRepository;
@Autowired
private SensitiveContentHitHistoryRepository sensitiveContentHitHistoryRepository;

private WordNode sensitiveWordNodeTree;

public SensitiveContentCheckResult check(String text) {
return check(text, true);
}

public SensitiveContentCheckResult check(String text, boolean generateRecord) {
if (sensitiveWordNodeTree == null) {
initSensitiveWordNodeTree();
}
Set<String> hits = findSensitiveWords(text);
if (hits != null && !hits.isEmpty() && generateRecord) {
generateSensitiveContentCheckHistory(text, hits);
}

return buildCheckResult(text, hits);
}

private void initSensitiveWordNodeTree() {
List<SensitiveText> all = sensitiveTextRepository.findAll();
buildDFATree(all.stream().map(SensitiveText::getContent).collect(Collectors.toList()));
}

private void buildDFATree(List<String> strings) {
WordNode root = new WordNode();
strings.forEach(word -> {
WordNode current = root;
for (int i = 0; i < word.length(); i++) {
char ch = word.charAt(i);
current.putChildIfAbsent(ch, new WordNode());
current = current.getChildren().get(ch);
if (i == word.length() - 1) {
current.setEnd(true);
}
}
});
sensitiveWordNodeTree = root;
}

private SensitiveContentCheckResult buildCheckResult(String text, Set<String> hits) {
SensitiveContentCheckResult result = new SensitiveContentCheckResult();
result.setCheckTime(LocalDateTime.now());
result.setSensitive(hits != null && !hits.isEmpty());
result.setSensitiveContent(hits);
result.setOriginalContent(text);
return result;

}

private Set<String> findSensitiveWords(String text) {
Set<String> hits = new HashSet<>();
for (int i = 0; i < text.length(); i++) {
int matchedLength = doCheck(sensitiveWordNodeTree.getChildren(), text, i);
if (matchedLength > 0) {
hits.add(text.substring(i, i + matchedLength));
i += matchedLength - 1;
}
}
return hits;
}

private int doCheck(Map<Character, WordNode> sensitiveWords, String txt, int beginIndex) {
if (sensitiveWords == null || sensitiveWords.isEmpty()) {
return 0;
}
boolean allMatches = false;
int matchedIndex = 0;
for (int i = beginIndex; i < txt.length(); i++) {
WordNode wordNode = sensitiveWords.get(txt.charAt(i));
if (wordNode == null) {
break;
}
matchedIndex++;
sensitiveWords = wordNode.getChildren();
if (wordNode.isEnd()) {
allMatches = true;
break;
}
}
return allMatches ? matchedIndex : 0;
}

private void generateSensitiveContentCheckHistory(String text, Set<String> sensitiveWordList) {
SensitiveContentHitHistory sensitiveContentHitHistory = new SensitiveContentHitHistory();
sensitiveContentHitHistory.setCheckedText(text);
sensitiveContentHitHistory.setSensitiveContent(String.join(SENSITIVE_TEXT_DELIMITER, sensitiveWordList));
sensitiveContentHitHistoryRepository.save(sensitiveContentHitHistory);
}

}

待拓展/完善的功能

  • 目前是敏感词完全匹配,实际运用中很多情况是模糊匹配, 可考虑支持通配符配置。例如,想要过滤掉:你***妈
  • 可考虑加入自动替换敏感词的功能,不然每一条都得去审核,工作量太大,并且一本检测出来的敏感词,审核了也不能让过~
  • 另外一些功能,比如要封号之类的,应该都是基于History。

示例代码库

https://github.com/jarryscript/sensitive-content-filter

SXK促销设计

修订人 时间 备注
周坚林 2021-09-18 初始化文档
周坚林 2021-09-22 完善内容
周坚林 2021-10-11 完善内容
周坚林 2021-11-03 根据实际情况修改设计

概述

促销是指平台、机构根据内外部条件,核算营销过程中发生的各项成本,为了争取最大经济效益,而进行的影响消费者购买态度和行为的一种营销方式,是一种重要的产品运营手段。其核心是怎样”算钱”。
促销系统作为整体系统的一个子模块,为商品价格展示、订单价格计算等场景提供基础服务。


促销的开展形式

  • 开展优惠活动

以开展促销活动形式将用户与促销关联,形成订单后,在订单支付之前,由用户自主选择是否参加活动,参加哪项活动,系统计算适用规则后自动应用

  • 发放/领取优惠卷

以发放优惠卷形式将用户与促销关联,形成订单后,由客户自主选择使用。
其中,优惠卷可以看作是一种特殊的促销活动,其特殊性在于某些优惠卷用户使用前可能需要进行领取,使用时用户需要自行选择,使用之后需要进行核销。优惠卷有用户主动领取,和系统自动发放两种


促销分类

按促销的应用对象来分类

  • 单品促销

直降 直接指定某单品降价或打折销售
赠品 购买某单品获赠
秒杀 限量低价商品竞拍
团购 批发

  • 订单促销

订单金额满减 , 每满减,阶梯满减
满折-同上
满返-满金额后送优惠卷
满赠-订单满额后附送赠品

  • 组合促销

套餐

  • 其它

定金抵扣



SXK中的订单与订单流程设计

在SXK中,只有单个简单订单概念,没有订单项/物流运输等复杂订单概念
预计未来也没有复杂订单场景,因此SXK中的促销调整对象为订单

SXK中共有3个触发促销计算的场景:

  • 课程信息查看

用户在查看课程信息时,可查看到促销信息

  • 课程订单支付

订单与一个课程关联,有课程数量概念,可退款,支付前可看到促销信息

  • 活动订单支付

与一个活动关联,没有活动数量概念,但有报名人数的概念,可退款,支付前可看到促销信息

促销计算将在此三处被触发。
支付之后,促销被应用,此后看到的订单上的信息为当时数据快照,查看时不会再次触发促销计算
此外退款时按照实际付款金额退款,需扣除促销部分,并采用价格分摊形式


SXK促销业务

SXK中,促销的参与者有
其中适用于全平台所有机构的促销称为平台级促销适用于机构的促销称为机构级促销

平台
发布平台级促销
发布机构促销
审核所有级别促销内容
编辑所有级别促销状态
监管方
查看所有促销情况
机构
发布机构促销
审核机构促销内容
终端用户
参与促销活动
领取优惠卷,使用优惠卷


计算的流程


退课流程 – pending


促销模型设计

数据库设计

数据库中主键全部采用long型
常量类型采用string, 以达到表意目的,在应用层中定义枚举类候选值

Promotion
促销表,促销活动与优惠卷的主表

属性名 类型 候选值 默认值 示例 说明
name string / / 三周年全场优惠活动
通用优惠卷 促销活动或优惠卷的名称
promotion_type string ACTIVITY
COUPON / / 促销活动
优惠卷
createDate date 创建日期
description string 描述
enabled boolean 是否启用
priority int / 1 优先级, 数字越小,优先级越高,按口语化来,例如“1级促销”,听起来优先级就很高
mediaLink String 相关媒体链接

PromotionRule
促销的限制条件

属性名 类型 候选值 默认值 说明
promotion_id long / / 关联的促销的ID
allowMultiple boolean / 是否允许叠加使用
beginUsable date / 可使用开始时间
endUsable date / 可使用结束时间
createDate date / 创建日期
description string / 描述
enabled boolean false 是否启用,默认不启用,必须审核
priority int 优先级, 数字越小,优先级越高,按口语化来,例如“1级促销”,听起来优先级就很高
startDate date 促销对外开始日期,例如优惠卷,先发给用户了,但暂时无法使用
endDate date 促销对外结束日期
evaluationLimit int / 1 允许叠加使用次数
minOrderAmount bigdecimal 最小订单价格
minPurchaseCount int 最少购买人数

PromotionAction
促销的实际动作,此表为promotion表拓展表

属性名 类型 候选值 默认值 说明
adjuster double / 0 折扣价 或者 折扣百分比,取决于打折类型
type string PERCENT_OFF
AMOUNT_OFF
FIXED_DISCOUNT PERCENT_OFF 百分比折扣
总价折扣
固定折价
createDate timestamp / 创建日期
description / 描述

PromotionHistory
促销发生历史,用于记录数据并做分析,每次实际促销将写入或修改数据

属性名 类型 候选值 说明
promotion_id long 关联的促销ID
user_id long 用户ID
order_id long 订单ID
create_time date 创建记录的时间

Coupon
优惠卷表, 为promotion的拓展表

属性名 类型 候选值 说明
promotion_id long 关联的促销的ID
couponCode string / 优惠卷码,自动生成
couponType string 优惠卷类型
publish_count int 发行数量
used_count int
已使用数量
claimed_count int 已领取数量

CouponHistory
优惠卷历史,为promotionHistory的拓展表,每次优惠卷操作将写入或修改一行数据

属性名 类型 候选值 说明
coupon_id long / 优惠卷ID
user_id long 用户ID
order_id long 订单ID
coupon_code string
优惠卷码,冗余
status string UNUSED / USED / EXPIRED 使用状态
use_time date 使用时间

GroupOrder
团购订单,拓展订单表

属性名 类型 候选值 说明
order_id long / 关联的order的ID
course_id long 关联的课程ID
bigdecimal 调整的价格
promotion_id long / 关联的促销的ID

OrderAdjustment
订单级别的促销应用结果,每条促销的应用将产生一行对应的数据

属性名 类型 候选值 说明
order_id long / 关联的order的ID
description string 描述,默认使用促销的描述
adjuster bigdecimal 调整的价格
promotion_id long / 关联的促销的ID

核心接口设计


使用案例

  • 拼团折扣-3人拼团购买课程打95折— pending

    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
    {
    "promotion":{
    "name": "3人拼团购买课程打95折",
    "promotionType":"ACTIVITY",
    "description":"三人组团购买课程价格95折优惠",
    "enabled":true,
    "priority":1,
    "mediaLink":"http://sample.jpg",
    "rule":{
    "allowMultiple":true,
    "startDate":"2021-09-09",
    "endDate":"2021-09-09",
    "beginUsable":"2021-09-09",
    "endUsable":"2021-09-09",
    "createDate":"2021-09-09",
    "description":"blablablabla..",
    "evaluationLimit":0,
    },
    "action":{
    "adjuster":0.95,
    "type":"PERCENT_OFF",
    "description":"打95折",
    }
    }

    }
  • 平台级别打折-周年庆全场满300元95折

    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
    {
    "promotion":{
    "name": "周年庆全场95折",
    "promotionType":"ACTIVITY",
    "description":"周年庆全场95折",
    "enabled":true,
    "priority":1,
    "mediaLink":"http://sample.jpg",
    "rule":{
    "allowMultiple":true,
    "startDate":"2021-09-09",
    "endDate":"2021-09-09",
    "beginUsable":"2021-09-09",
    "endUsable":"2021-09-09",
    "createDate":"2021-09-09",
    "description":"blablablabla..",
    "evaluationLimit":0,
    "minOrderAmount":300
    },
    "action":{
    "adjuster":0.95,
    "type":"PERCENT_OFF",
    "description":"打95折",
    }
    }

    }
  • 发放满减优惠卷-使用满1000减1毛优惠卷

    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
    {
    "coupon":{
    "name": "满1000减1毛优惠卷",
    "promotionType":"COUPON",
    "description":"满1000减1毛优惠卷",
    "enabled":true,
    "priority":1,
    "mediaLink":"http://sample.jpg",
    "couponCode":"#284NJF",
    "publish_count":"99999",
    "used_count":"0",
    "claimed_count":"0",
    "rule":{
    "allowMultiple":false, // 代表不能多次应用,如果是每满1000减1毛,那么这里设置成true
    "startDate":"2021-09-09",
    "endDate":"2021-09-09",
    "beginUsable":"2021-09-09",
    "endUsable":"2021-09-09",
    "createDate":"2021-09-09",
    "description":"blablablabla..",
    "evaluationLimit":0,
    "minOrderAmount":300
    },
    "action":{
    "adjuster":0.1,
    "type":"AMOUNT_OFF",
    "description":"满1000减1毛",
    }
    }

    }

Troubleshooting

数字建造产业平台-记账模块设计

版本 1.0
上次更新:  4/06/2021  
 
修订历史  

|
时间 |
版本 |
作者 |

描述 |
| — | — | — | — |
|
 2020-04-06 |
1.0 |
周坚林  |
文档初始化 |

 
参考文件

|
文档 |

链接 |
| — | — |
|
  |
  |

**

本文档为数字建造产业平台商户记账模块设计文档。
本文档主要使用对象有: 开发、测试、产品经理、项目经理、交互设计师、运营及其他相关业务人员。
本文档目的为指导开发人员获知产品逻辑,指导测试人员编写测试用例,指导项目经理拆分工作包,指导原型与交互设计,并作为项目启动、验收等环节评审依据.

**

  • 可应用于供应链平台上的账务记录

  • 复式记账,记录原始凭证

  • 客户端上,可提供账务的不同业务视图,如应收/应付/冻结/债务等

  • 系统记录的出纳流水,原始凭证,后续可按标准会计科目入账,并产生标准财务报表,如常规四表一注

  • 提供记账API

**

应收应付制
应收应付制又称债权发生制,是会计常用的两种记账方式之一,另外一种为收付实现制
甲对乙说,我给你100块钱,此时
乙拿出了账本,写下 我明天应收到甲100块钱—— 应收应付制
第二天甲给乙钱的时候,乙拿出了账本,写下 甲给了的100块钱 —— 收付实现制
权责发生制在反映企业的经营业绩时有其合理性,几乎完全取代了收付实现制

**

**

|
记账点 |
借方 |
|
|
|
|
| — | — | — | — | — | — |
|
|
应收 |
应付 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|

|
记账点 |
描述 |
借方账本 |
借方条目 |
贷方账本 |

贷方条目 |
| — | — | — | — | — | — |
|
充值 |
商户现金充值到平台 |
银行 |
现金 |
现金 |
现金 |
|
冻结托管资金
|
根据epc资金托管合同,冻结相应资金 |
现金 |
现金 |
应付 |
处理中 |
|
贷款 |
分包商以分包合同预期收益做抵押,向平台申请贷款 |
应收 |
现金 |
现金 |
冻结 |
|
支付采买合同 |
分包商使用现金或贷款支付材料采买合同
|
|
|
|
|

  • 工程款分期结算

工程分期验收结束后,从建设方托管的资金中支付给分包商
工程分期验收结束后,分包商归还平台相应贷款
工程分期验收结束后,分包商支付平台贷款利息
工程分期验收结束后,分包商支付平台贷款罚息
工程分期验收结束后,分包商支付平台信息技术服务费

  • 提现

商户从现金账户中,提取资金到银行账户

**

**

工地现场劳务管理方案设计

版本 1.0
上次更新:  03/10/2021  
 
修订历史  

时间 版本 作者 描述
2020-3-10 1.0 周坚林 初始化文档
2020-5-15 1.1 周坚林 完善方案设计详情
2020-5-17 1.1 周坚林 完善方案设计详情

 
参考文件

文档 链接

1. 业务概述

1.1 概述

进场手续办理
现场考勤
退场手续办理
劳务工资结算

1.2 参考资料

**2 方案设计 **

2.1 业务流程设计

本系统开发完成后的用途,能够产生的效果;

2.2 功能详细描述

2.2.1 劳务监督物联网设备管理

2.2.1.1 设备安装

业务流程中涉及以下三种设备:

  • 人证核验机
  • 人脸识别机
  • 闸机
  • 高拍仪及其他拍照摄像设备
  1. 人证核验机 - 熵基ID830-H

熵基ID830-H 需要搭建 通讯中间件 DataBus, 以以下方式工作
具体安装方式参考熵基官方文档

  1. 人脸识别机 - 熵基XFACE600

熵基XFACE600 以HTTP Rest 方式与劳务监督模块直接交互
具体安装方式参考熵基官方文档

  1. 闸机 - 略
  2. **高拍仪或其他拍照设备 - **作为辅助输入设备,不做特殊管理

2.2.1.2 绑定设备到项目

对于各类物联网设备,有以下统一设计

2.2.2 班组与考勤信息管理

2.2.2.1 班组信息管理

项目中分为多个班组

2.2.2.2 考勤规则配置

每个班组有多个工作时间段
每个时间段属性包含
最早开始时间
开始时间
最晚开始时间

最早结束时间
结束时间
最晚结束时间

2.2.3 入场手续办理

施工人员
入场前人证合一校验,产生入场手续待办事项
管理人员辅助入场人员进行信息填写,包含
联系方式
班组信息
劳务合同信息
银行卡信息
信息填写完成后打印成册,双方确认无误后盖章并上传扫描件,至此入场手续办理完成,随后
系统向考勤设备下发人证核验后的人脸信息

2.2.4 日常考勤

考勤判断逻辑

2.2.5 退场手续办理

施工人员退场时需携带身份证,通过退场人脸核验机器产生退场待办事项
工作人员协助办理退场,打印
退场承诺书
打印考勤报表
系统根据劳务合同结算劳务工资,打印工资条
双方确认无误后盖章,上传退场承诺见证照片,至此退场手续办理完成

2.3 数据库设计

2.4 接口设计

2.5 测试用例

请我喝杯咖啡吧

微信