这是一篇记录如何从建仓开始到最后安全测试完整流程的笔记,使用的spring生态,目的是为spring的基础后端开发及后期渗透测试打一个模板。本篇采用springSecurity作为安全框架,搭载了redis-cache、spring-valid等功能,并开放了OAuth2授权登录。
本篇代码格式规范为优雅,代码大量使用java8新特性以及apache包下方便的工具类,能一行代码写完的就不用两行。当然本文也配备了大量注释,方便有需要的同学学习。
限于篇幅,文章内只能简短提及使用原因以及简略使用方法,具体步骤还请参考本篇附带代码或百度
代码仓库:https://github.com/23DAY01/blog_23DAY
项目初始化
创建git仓库
github上创建一个仓库,复制仓库地址
https://github.com/xxx/xxx.git
创建spring项目
文件->新建->来自版本控制的项目->填写仓库地址
文件初始化
根据项目需要,更改.gitignore并修改配置文件
测试提交
- git命令行 
  - git add –ignore-error **.java
- git commit -m 'init'
- git push --progress --porcelain origin refs/heads/master:master
 
- 用idea的GUI直接提交也可以
JavaBean
设计原则
- 设置逻辑删除字段
- 设置生成时间、更新时间字段
- 单一关联时可违反第三范式
- 不设置外键
数据表
为不占用篇幅,数据表放在篇末
生成实体类
诸多工具可以实现从数据库直接生成pojo实体类,本篇采用mybatis plus
Mybatis-Plus生成器生成domain、dto、vo、controller、service、serviceImpl、mapper、mapperXml
生成器模板具体见mybatis-plus使用手册
领域驱动
这里简单描述一下领域驱动,他主要的目的是为了降低各层之间的耦合,业务层只做业务逻辑
| 对象 | 含义 | 作用 | 作用范围 | 
|---|---|---|---|
| DO(Data Object) | 数据对象 | 与数据库表结构对应,通过DAO层向上传输数据源对象 | 数据库与Dao层,Dao层与Service层 | 
| DTO(Data Transfer Object) | 数据传输对象 | 主要用于远程调用等需大量传输对象的地方 | Sevice层和Web层 | 
| BO(Business Object) | 业务对象 | 业务对象,可以由 Service 层输出的封装业务逻辑的对象 | Sevice层和Web层 | 
| VO(View Object) | 视图对象 | 对应页面所显示的数据,将这些数据进行封装并返回 | Web层与页面 | 
| POJO(Plain Ordinary Java Object) | 普通Java对象 | - | - | 
盗用别人的一张图来描述领域驱动
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zm4VS8vk-1675427351964)(img/%E6%90%AD%E5%BB%BA%E6%B5%81%E7%A8%8B/image-20230122132819257.png)]](https://img-blog.csdnimg.cn/b49052d75e2a4989b9368c922f6c2cc6.png)
当业务不复杂的时候BO和DTO可以合成一个,VO可以直接使用DTO
概念是死的,人是活的,当团队协作开发时一定要保证大家理解的概念是一样的
本篇采用的是domain、dto、vo三层
JavaBean转换
对于pojo封装的实体类在各层之间的转换,可以采用工具进行转换
- mapStruct
- spring
- hutools
- EntityUtils
- BeanCopier
- ……
本篇采用的是mapStruct进行实体类转化
引入依赖,编写转换器即可使用。mapStruct在打包后会自动生成一段转换代码,并不是使用反射的方式去进行javabean的转换。在转换的过程中,字段名相同的字段会直接转换,不同的字段需要使用@Mapping注解定义,对于嵌套的字段,mapStruct会在自身中自动寻找对应的转换方法。
@Mapper(componentModel = "spring" , injectionStrategy = InjectionStrategy.CONSTRUCTOR)
@Component
public interface MapStruct {
    @Mappings({
            @Mapping(target = "id",source = "studentId"),
    })
    Student studentConvert(StudentRequestVo studentRequestVo);
}
API文档
业务状态码
目前大家对于业务状态码的评论出现两极分化状态,详见https://www.v2ex.com/t/611572
本篇采用业务状态码,并参考微信小程序官方给出的状态码设计规则,自定义状态码
- 前三位遵循http状态码:200、400、500
- 中间两位表示错误范围(file、param、authentication等)
- 后两位表示细粒度错误类型
业务状态码已放在篇末
swagger/knife4j
生成接口文档,导入包并配置文件Knife4jConfig,使用@Api @ApiOperation @ApiParam等标注方法和类,在写完业务逻辑后访问配置文件中配置的文档地址,即可查看全部标注过的接口
Knife4j是一个集Swagger2 和 OpenAPI3为一体的增强解决方案
接口设计
RESTful 的核心思想就是,客户端发出的数据操作指令都是"动词 + 宾语"的结构。比如,GET /articles这个命令,GET是动词,/articles是宾语。
- GET:读取(Read)
- POST:新建(Create)
- PUT:更新(Update)
- PATCH:更新(Update),通常是部分更新
- DELETE:删除(Delete)
宾语就是 API 的 URL,是 HTTP 动词作用的对象。它应该是名词,不能是动词。比如,/articles这个 URL 就是正确的
耦合处理
通过构建常量、枚举、工具等类降低项目耦合性,减小后期维护难度
枚举常量
constant
| 常量类 | 描述 | 
|---|---|
| AuthConst | 认证常量,token过期时间、随机数种子等 | 
| CommonConst | 公共常量 | 
| DateConst | 日期常量,借鉴hutool封装的日期常量 | 
| OptTypeConst | 操作日志常量,操作类型等 | 
| RedisPrefixConst | redis常量,用于生成redis的key | 
| SocialLoginConst | 登录常量,oauth2相关常量 | 
| StatusMsgConst | 业务状态码常量 | 
| WebConst | 数据包常量 | 
enums
- FileExtEnum
- FilePathEnum
- LoginTypeEnum
- StatusCodeEnum
- UploadModeEnum
工具类
如果想自己造轮子可以仿照诸多工具去自己造,不想造的话直接调用即可
| 工具类 | 描述 | 
|---|---|
| ArithmeticUtil.java | 浮点数算数工具,用于提供server性能计算 | 
| AuthUtil.java | 认证工具,用于支持SpringSecurity | 
| BeanCopyUtil.java | 实体类转换工具,spring自带工具 | 
| ConvertUtil.java | 类型转换工具 | 
| DateUtil.java | 日期工具 | 
| FileUtil.java | 文件工具 | 
| JsonUtil.java | Json处理工具 | 
| JwtUtil.java | JWT工具,用于支持SpringSecurity | 
| MapStruct.java | 实体类转换工具,mapStruct | 
| OssUtil.java | OSS工具 | 
| RedisUtil.java | redis工具 | 
| ResponseAPI.java | 封装响应体 | 
| EncryptUtil.java | 加解密工具 | 
| StringUtil.java | 字符串工具 | 
| WebUtil.java | 网络工具,处理数据包 | 
异常处理
GlobalExceptionHandler利用@RestControllerAdvice和@ExceptionHandler注解实现异常统一处理
目前对于异常自定义的写法有很多,本篇采用的是根据多篇文章和Java特性自己总结出来的一种写法,主旨依旧是降低耦合、提高复用性
自定义异常,并继承BaseException,复写withErrorMessage和withErrorCodeEnum即可
对于异常处理的流程:
-  DAO - 尽量不 catch 任何异常, 该向上抛就抛
- 不用记录 log 日志, 或者仅使用 logger.debug() 记录
 
-  Service - 对于一些关键问题,应该及时 throw异常, 以确保事务完整
- Service 层一般的日志级别, 应该用 logger.debug() 记日志
 
- 对于一些关键问题,应该及时 
-  Controller 层: - Controller 层负责组装 Service, 在关键步骤上应该加日志输出 (info 级别)
- Controller 层不应再主动 throw 异常
 
-  统一异常处理层: 通过 json 或 UI 返回详细的报错信息, 包括 HttpStatus 和详尽的 ErrorCode/ErrorMessage 
日志框架
本篇采用slf4j+logback记录日志
导入包并进行文件配置logback-spring.xml`
可以通过@Log4j2注解,在方法内部直接使用log即可
缓存技术
为了提高性能,引入缓存技术,目前springCache的实现有很多种
- GENERIC
- JCACHE
- EHCACHE
- HAZELCAST
- INFINISPAN
- COUCHBASE
- REDIS
- CACHE2K
- CAFFEINE
- SIMPLE
- NONE
本篇采用redis,继承CachingConfigurerSupport复写配置方法即可自定义key与过期时间等
首先连接redis,进行文件配置RedisConfig,编写redisUtil方便redis数据操作,编写配置文件CacheConfig并选定redis作为缓存类型即可
注解:
- @Cacheable:当重复(n>1)调用该方法时,不次执行方法体,其结果直接从缓存中找到并返回
- @CachePut:确保方法调用即执行,执行后更新缓存
- @CacheEvict:- @Cachable注解的反向操作,它负责从给定的缓存中移除一个值
- @Caching:- @Caching是一个组注解,可以为一个方法定义提供基于- @Cacheable、- @CacheEvict或者- @CachePut注解的数组
- @CacheConfig:在- @CacheConfig注解中定义了类级别的缓存和自定义键生成器
切面编程
spring里可以有三种方法实现切面编程,过滤器、拦截器、切面
三者的顺序为:过滤器->拦截器->切面
可以根据业务需要使用三者中的任意一个进行业务逻辑的编写
拦截器可以访问controller上下文、值栈里的对象,而过滤器不能访问
拦截器可以获取IOC容器中的各个bean,而过滤器就不行
操作日志
如果有对人员的行为进行记录的需求,则可以通过自定义注解OptLog,然后写一个切面OptLogAspect,并配置切入点即可实现对重点资产的操作行为记录
接口限流
实现对部分接口的接口限流操作,防止人员恶意访问接口。通过自定义注解AccessLimit,通过实现HandlerIntercept生成自定义拦截器ApiAccessRestrictionInterceptor,注入到spring里面,并在WebMvcConfigurer中添加拦截器
重复提交限制
实现对部分接口的重复提交限制。通过自定义注解RepeatSubmit,通过实现HandlerIntercept生成自定义拦截器RepeatSubmitInterceptor,注入到spring里面,并在WebMvcConfigurer中添加拦截器
安全框架
认证安全
本篇实现的是1.5次开发的springSecurity,复写了一部分的springSecurity的处理器,实现了部分的定制,这一部分内容推荐大家自己debug一下源码,这样会更容易理解
采用session管理用户,session信息保存在redis里面
所有bean注入全部放入SecurityBeanCreateConfig
本篇由于重写了UsernamePasswordAuthenticationFilter,所以对于springSecurity的原有配置做了比较大的改动,配置起来较为麻烦
认证
继承WebSecurityConfigurerAdapter对configure复写,对http进行配置
- formLogin 
  - 复写loadUserByUsername
- 认证成功处理器authenticationSuccessHandler
- 认证失败处理器authenticationFailHandler
- 登录url
 
- 复写
- logout 
  - 登出成功处理器logoutSuccessHandler
- 登出url
 
- 登出成功处理器
- authorizeRequests 
  - 密码比对策略DaoAuthenticationProviderImpl
- 自定义接口拦截规则filterInvocationSecurityMetadataSource
- 自定义权限提取规则accessDecisionManager
 
- 密码比对策略
- rememberMe
- 其他配置 
  - 前认证检查preAuthenticationChecks
- 后认证检查postAuthenticationChecks
 
- 前认证检查
会话管理
利用@EnableRedisHttpSession开启redis管理session
在WebSecurityConfig中配置http.session
- sessionId策略
- session过期策略
- session并发策略
- session集群会话处理
- session错误策略
上述部分策略由CompositeSessionAuthenticationStrategy集合后注入到LoginAuthenticationFilter
其余策略注入到springSecurity配置的session管理器中
验证码
本篇采用图形验证码,先编写配置文件CaptchaConfig,VerificationCodeFilter实现OncePerRequestFilter
在WebSecurityConfig中的http.addFilterAt将VerificationCodeFilter添加过滤器在UsernamePasswordAuthenticationFilter在前面即可
数据流恢复
通过BodyReaderRequestWrapper和RepeatableFilter恢复数据流,通过repeatableFilterRegistration方法注册,解决在Filter中读取Request中的流后,后续controller或restful接口中无法获取流的问题
将取出来的字符串,再次转换成流,然后把它放入到新request 对象中,在chain.doFiler方法中 传递新的request对象
网络配置
实现WebMvcConfigurer,配置定义的拦截器,解决跨域问题以及资源处理器问题
数据校验
由于对外接口全部体现为VO,我们在VO的对应字段上加入@Valid的相关注解,并在参数位置使用@Valid即可实现对数据的校验
- 针对 UI 输入检查, 如果 js 前端检查有困难, 可以在 Controller 层使用 Pojo validation 手段做检查, 然后前端使用 ajax 拿到校验结果. 检查过程没有触发 UI 完整渲染, 用户体验会很好
- Controller 层使用 validation 进行检查, 可以在视图函数的形参上检查, 或者在视图函数内部检查.
- Service 层, 使用 Assert 进行数据验证
- DAO 层, 不做任何数据验证
会话监听
通过session监听器对session的创建、销毁等行为进行自定义,其中就包括存在于session里面的id,即每次会话产生一次作用的javabean
RedisHttpSessionListener通过实现HttpSessionListener能监听session的创建销毁,实现HttpSessionAttributeListener能监听session里面的键值的创建销毁,然后将RedisHttpSessionListener作为bean注入到spring里面就可以实现监听了
注入检测
通过加一层过滤器实现对输入的安全过滤
定义过滤器后写一个wrapper把过滤器增强,重写里面的getParam等方法,然后将过滤器加入过滤器链中,从而实现过滤
策略
文件上传策略
通过构建文件上传上下文UploadStrategyContext来存储全局文件上传参数,UploadStrategy接口和AbstractUploadStrategyImpl抽象类,通过继承可实现本地文件上传LocalUploadStrategyImpl,oss文件上传OssUploadStrategyImpl等上传策略
登录策略
策略构建方法同上,可以构建qq登录、微博登录、邮箱登录方法
业务逻辑
介绍本篇重要利用的某些语法,主要为java8新特性、自定义分页、自定义响应包等
Optional
主要用于对null的判断,利用ofNullable、isPresent等
Stream
流的操作包含如下三个部分:创建流、中间流、关闭流。筛选、去重、映射、排序属于流的中间操作,收集属于终止操作。Stream是流操作的基础关键类
通过流操作可以更优雅快速的实现对javaBean的处理
先通过stream生成流,再通过map、distinct等处理流,最后收集流
分页
在mybatisplus自带的page的基础上构建PageUtil,构建了一个仅存在于一次request的page
通过一个过滤器实现对参数内current、size的获取,在控制层之前构建出page,从而实现在参数内无需出现分页参数
通过PageUtil可以解决返回参数时无法知道total的问题
最后通过pageResult封装一下分页bean即可
注意:page使用前后是一个实体
CompletableFuture
使用CompletableFuture可以实现异步任务,当前后业务逻辑无关时可以将耗时较长的方法设为异步任务
ResponseAPI
通过自定义响应包,将业务状态码与状态信息、响应体封装在一起
collections4
对于mybatis-plus的lambdaQuery或contains等需要判空的地方可以使用emptyIfNull、isNotEmpty等方法,方便编写业务逻辑
定时任务
对于游客用户的地域统计可以采用定时任务的形式,统计数据存入redis即可
上线部署
本篇用的是docker-compose去部署这个博客系统,在application.yml中将active字段设置为prod,然后创建application-prod.yml配置文件,将上线部署的环境变量配置进去即可,避免将本地配置暴露
把利用到的中间件等配置文件写好,利用docker-compose去构建这些docker容器即可,记得将容器放在一个网络内,而每个容器的ip或域名与其名字是一样的,也就是我们在application-prod.yml中可以直接使用容器名作为ip
另外,本篇用另一个域名处理管理员对应操作,nginx和vue打包时需要对应两个文件
安全测试
容灾备份
The end
数据表
day_article文章表
| 字段名 | 字段类型 | 属性 | 备注 | 
|---|---|---|---|
| id | int | not null | 主键 | 
| user_id | int | not null | 作者id | 
| category_id | int | null;default null | 分类id | 
| article_cover | varchar | null;default null | 文章封面 | 
| article_content | longtext | not null | 文章内容 | 
| article_title | varchar | not null | 文章标题 | 
| type | int | not null;default 0(1原创 2转载 3翻译) | 文章类型 | 
| original_url | varchar | null;default null | 原文链接 | 
| is_top | tinyint | not null;default 0(0否 1是) | 是否置顶 | 
| status | int | not null;default 1(1公开 2私密 3草稿) | 文章状态 | 
| deleted | tinyint | null;default 0(0否 null是) | 逻辑删除 | 
| create_time | datetime | not null | 创建时间 | 
| update_time | datetime | null;default null | 修改时间 | 
day_article_tag文章标签关系表
| 字段名 | 字段类型 | 属性 | 备注 | 
|---|---|---|---|
| id | int | not null | 主键 | 
| article_id | int | not null | 文章id | 
| tag_id | int | not null | 标签id | 
| deleted | tinyint | null;default 0(0否 null是) | 逻辑删除 | 
| create_time | datetime | not null | 创建时间 | 
| update_time | datetime | null;default null | 修改时间 | 
day_category分类表
| 字段名 | 字段类型 | 属性 | 备注 | 
|---|---|---|---|
| id | int | not null | 主键 | 
| category_name | varchar | not null | 分类名称 | 
| deleted | tinyint | null;default 0(0否 null是) | 逻辑删除 | 
| create_time | datetime | not null | 创建时间 | 
| update_time | datetime | null;default null | 修改时间 | 
day_chat_record聊天记录表
| 字段名 | 字段类型 | 属性 | 备注 | 
|---|---|---|---|
| id | int | not null | 主键 | 
| user_id | int | null;default null | 用户id | 
| nickname | varchar | not null | 用户昵称 | 
| avatar | varchar | not null | 用户头像 | 
| content | varchar | not null | 聊天内容 | 
| ip_address | varchar | not null | ip地址 | 
| ip_source | varchar | not null | ip来源 | 
| type | int | not null | 记录类型 | 
| deleted | tinyint | null;default 0(0否 null是) | 逻辑删除 | 
| create_time | datetime | not null | 创建时间 | 
| update_time | datetime | null;default null | 修改时间 | 
day_comment评论表
除一级评论(顶级评论)外,其他嵌套评论均归为二级评论
parent_id是父评论id
top_id是顶级评论
reply_user_id是当前comment作为回复时,被回复的评论的用户id,属于冗余设计
user_id是当前comment的用户id
affiliation_id是属主的id
type是属主的类型
| 字段名 | 字段类型 | 属性 | 备注 | 
|---|---|---|---|
| id | int | not null | 主键 | 
| user_id | int | not null | 用户id | 
| affiliation_id | int | null;default null | 所属主体id | 
| type | int | not null;default 0(1文章 2友链 3说说) | 评论类型 | 
| comment_content | text | not null | 评论内容 | 
| reply_user_id | int | null;default null | 回复用户id | 
| parent_id | int | null;default null | 父评论id | 
| top_id | int | null;default null | 顶级评论id | 
| is_review | tinyint | not null;default 0(0否 1是) | 是否审核 | 
| deleted | tinyint | null;default 0(0否 null是) | 逻辑删除 | 
| create_time | datetime | not null | 创建时间 | 
| update_time | datetime | null;default null | 修改时间 | 
day_friend_link友链表
| 字段名 | 字段类型 | 属性 | 备注 | 
|---|---|---|---|
| id | int | not null | 主键 | 
| link_name | varchar | not null | 链接名称 | 
| link_avatar | varchar | not null | 链接头像 | 
| link_address | varchar | not null | 链接地址 | 
| link_info | varchar | not null | 链接简介 | 
| deleted | tinyint | null;default 0(0否 null是) | 逻辑删除 | 
| create_time | datetime | not null | 创建时间 | 
| update_time | datetime | null;default null | 修改时间 | 
day_menu菜单表
| 字段名 | 字段类型 | 属性 | 备注 | 
|---|---|---|---|
| id | int | not null | 主键 | 
| menu_name | varchar | not null | 菜单名称 | 
| menu_path | varchar | not null | 菜单路径 | 
| compoent | varchar | not null | 组件 | 
| icon | varchar | not null | 菜单图标 | 
| order_num | int | not null | 排序级别 | 
| parent_id | int | not null | 父菜单id | 
| is_hidden | tinyint | not null;default 0(0否 1是) | 是否隐藏 | 
| deleted | tinyint | null;default 0(0否 null是) | 逻辑删除 | 
| create_time | datetime | not null | 创建时间 | 
| update_time | datetime | null;default null | 修改时间 | 
day_message留言表
| 字段名 | 字段类型 | 属性 | 备注 | 
|---|---|---|---|
| id | int | not null | 主键 | 
| nickname | varchar | not null | 用户昵称 | 
| avatar | varchar | not null | 用户头像 | 
| message_content | varchar | not null | 留言内容 | 
| ip_address | varchar | not null | ip地址 | 
| ip_source | varchar | not null | ip来源 | 
| speed | int | null;default null | 弹幕速度 | 
| is_review | tinyint | not null;default 0(0否 1是) | 是否审核 | 
| deleted | tinyint | null;default 0(0否 null是) | 逻辑删除 | 
| create_time | datetime | not null | 创建时间 | 
| update_time | datetime | null;default null | 修改时间 | 
day_operation_log操作日志表
| 字段名 | 字段类型 | 属性 | 备注 | 
|---|---|---|---|
| id | int | not null | 主键 | 
| opt_module | varchar | not null | 操作模块 | 
| opt_type | varchar | not null | 操作类型 | 
| opt_url | varchar | not null | 操作url | 
| opt_method | varchar | not null | 操作方法 | 
| opt_desc | varchar | not null | 操作描述 | 
| request_param | longtext | not null | 请求参数 | 
| request_method | varchar | not null | 请求方式 | 
| response_data | longtext | not null | 响应数据 | 
| user_id | int | not null | 用户id | 
| nickname | varchar | not null | 用户昵称 | 
| ip_address | varchar | not null | ip地址 | 
| ip_source | varchar | not null | ip来源 | 
| deleted | tinyint | null;default 0(0否 null是) | 逻辑删除 | 
| create_time | datetime | not null | 创建时间 | 
| update_time | datetime | null;default null | 修改时间 | 
day_page页面表
| 字段名 | 字段类型 | 属性 | 备注 | 
|---|---|---|---|
| id | int | not null | 主键 | 
| page_name | varchar | not null | 页面名称 | 
| page_label | varchar | null;default null | 页面标签 | 
| page_cover | varchar | not null | 页面封面 | 
| deleted | tinyint | null;default 0(0否 null是) | 逻辑删除 | 
| create_time | datetime | not null | 创建时间 | 
| update_time | datetime | null;default null | 修改时间 | 
day_resource资源表
| 字段名 | 字段类型 | 属性 | 备注 | 
|---|---|---|---|
| id | int | not null | 主键 | 
| resource_name | varchar | not null | 资源名称 | 
| url | varchar | null;default null | 权限路径 | 
| request_method | varchar | null;default null | 请求方式 | 
| parent_id | int | null;default null | 父权限id | 
| is_anonymous | tinyint | not null;default 0(0否 1是) | 是否可以匿名访问 | 
| deleted | tinyint | null;default 0(0否 null是) | 逻辑删除 | 
| create_time | datetime | not null | 创建时间 | 
| update_time | datetime | null;default null | 修改时间 | 
day_role角色表
| 字段名 | 字段类型 | 属性 | 备注 | 
|---|---|---|---|
| id | int | not null | 主键 | 
| role_name | varchar | not null | 角色名称 | 
| role_label | varchar | not null | 角色描述 | 
| is_disable | tinyint | not null;default 0(0否 1是) | 是否禁用 | 
| deleted | tinyint | null;default 0(0否 null是) | 逻辑删除 | 
| create_time | datetime | not null | 创建时间 | 
| update_time | datetime | null;default null | 修改时间 | 
day_role_menu角色菜单表
| 字段名 | 字段类型 | 属性 | 备注 | 
|---|---|---|---|
| id | int | not null | 主键 | 
| role_id | int | null;default null | 角色id | 
| menu_id | int | null;default null | 菜单id | 
| deleted | tinyint | null;default 0(0否 null是) | 逻辑删除 | 
| create_time | datetime | not null | 创建时间 | 
| update_time | datetime | null;default null | 修改时间 | 
day_role_resource角色资源表
| 字段名 | 字段类型 | 属性 | 备注 | 
|---|---|---|---|
| id | int | not null | 主键 | 
| role_id | int | null;default null | 角色id | 
| resource_id | int | null;default null | 资源id | 
| deleted | tinyint | null;default 0(0否 null是) | 逻辑删除 | 
| create_time | datetime | not null | 创建时间 | 
| update_time | datetime | null;default null | 修改时间 | 
day_tag标签表
| 字段名 | 字段类型 | 属性 | 备注 | 
|---|---|---|---|
| id | int | not null | 主键 | 
| tag_name | varchar | not null | 标签名称 | 
| deleted | tinyint | null;default 0(0否 null是) | 逻辑删除 | 
| create_time | datetime | not null | 创建时间 | 
| update_time | datetime | null;default null | 修改时间 | 
day_talk说说表
| 字段名 | 字段类型 | 属性 | 备注 | 
|---|---|---|---|
| id | int | not null | 主键 | 
| user_id | int | not null | 用户id | 
| content | varchar | not null | 说说内容 | 
| image | varchar | null;default null | 图片 | 
| is_top | tinyint | not null;default 0(0否 1是) | 是否置顶 | 
| status | tinyint | not null;default 1(0公开 1私密) | 说说状态 | 
| deleted | tinyint | null;default 0(0否 null是) | 逻辑删除 | 
| create_time | datetime | not null | 创建时间 | 
| update_time | datetime | null;default null | 修改时间 | 
day_view访问量表
| 字段名 | 字段类型 | 属性 | 备注 | 
|---|---|---|---|
| id | int | not null | 主键 | 
| view_count | int | not null | 访问量 | 
| deleted | tinyint | null;default 0(0否 null是) | 逻辑删除 | 
| create_time | datetime | not null | 创建时间 | 
| update_time | datetime | null;default null | 修改时间 | 
day_user_auth用户权限表
| 字段名 | 字段类型 | 属性 | 备注 | 
|---|---|---|---|
| id | int | not null | 主键 | 
| user_info_id | int | not null | 用户信息id | 
| username | varchar | not null | 用户名 | 
| password | varchar | not null | 密码 | 
| login_type | int | not null | 登录类型 | 
| ip_address | varchar | null;default null | ip地址 | 
| ip_source | varchar | null;default null | ip来源 | 
| is_disabled | tinyint | not null;default 0(0否 1是) | 是否禁用 | 
| last_login_time | datetime | null;default null | 上次登录时间 | 
| deleted | tinyint | null;default 0(0否 null是) | 逻辑删除 | 
| create_time | datetime | not null | 创建时间 | 
| update_time | datetime | null;default null | 修改时间 | 
day_user_info用户信息表
| 字段名 | 字段类型 | 属性 | 备注 | 
|---|---|---|---|
| id | int | not null | 主键id | 
| varchar | null;default null | 邮箱 | |
| nickname | varchar | not null | 用户昵称 | 
| avatar | varchar | not null;default ‘’ | 用户头像 | 
| intro | varchar | null;default null | 用户介绍 | 
| website | varchar | null;default null | 个人网站 | 
| deleted | tinyint | null;default 0(0否 null是) | 逻辑删除 | 
| create_time | datetime | not null | 创建时间 | 
| update_time | datetime | null;default null | 修改时间 | 
day_user_role用户角色表
| 字段名 | 字段类型 | 属性 | 备注 | 
|---|---|---|---|
| id | int | not null | 主键 | 
| user_id | int | null;default null | 用户id | 
| role_id | int | null;default null | 角色id | 
| deleted | tinyint | null;default 0(0否 null是) | 逻辑删除 | 
| create_time | datetime | not null | 创建时间 | 
| update_time | datetime | null;default null | 修改时间 | 
day_website_config网站配置表
| 字段名 | 字段类型 | 属性 | 备注 | 
|---|---|---|---|
| id | int | not null | 主键 | 
| name | varchar | not null | 网站名称 | 
| intro | varchar | null;default null | 网站简介 | 
| author | varchar | null;default null | 网站作者 | 
| url | varchar | null;default null | 网站地址 | 
| notice | varchar | null;default null | 网站通知 | 
| about | varchar | null;default null | 关于我 | 
| github | varchar | null;default null | github | 
| varchar | null;default null | qq号 | |
| beian_id | varchar | null;default null | 备案号 | 
| author_avatar | varchar | null;default ‘’ | 作者头像 | 
| user_avatar | varchar | null;default‘’ | 游客头像 | 
| deleted | tinyint | null;default 0(0否 null是) | 逻辑删除 | 
| create_time | datetime | not null | 创建时间 | 
| update_time | datetime | null;default null | 修改时间 | 
业务状态码
2xx:
| http状态码 | 错误范围 | 细粒度错误 | 业务状态码 | 错误原因 | 错误名称 | 
|---|---|---|---|---|---|
| 200 | 00 | 00 | 200000 | null | OK | 
4xx(client):
| http状态码 | 错误范围 | 细粒度错误 | 业务状态码 | 错误原因 | 错误名称 | 
|---|---|---|---|---|---|
| 400 | 01 | 01 | 4000101 | 参数缺失 | ACCESS_PARAM_MISSING | 
| 400 | 01 | 02 | 4000102 | 参数类型错误 | ACCESS_PARAM_TYPE_ERROR | 
| 400 | 01 | 03 | 4000103 | 参数校验错误 | ACCESS_PARAM_NOT_VALID | 
| 400 | 02 | 01 | 4000201 | 文件为空 | FILE_EMPTY | 
| 400 | 02 | 02 | 4000202 | 文件类型错误 | FILE_TYPE_ERROR | 
| 400 | 03 | 01 | 4000301 | 尚未登录 | AUTH_NO_LOGIN | 
| 400 | 03 | 02 | 4000302 | 缺少权限 | AUTH_PERMISSION_DENIED | 
| 400 | 03 | 03 | 4000303 | 认证失败 | AUTH_UorP_ERROR | 
| 400 | 03 | 04 | 4000304 | 用户名为空 | AUTH_USERNAME_EMPTY | 
| 400 | 03 | 05 | 4000305 | 账号不存在 | AUTH_USER_NOT_FOUND | 
| 400 | 03 | 06 | 4000306 | 认证失败 | AUTH_FAILED | 
| 400 | 03 | 07 | 4000307 | 校验码错误 | AUTH_CODE_ERROR | 
| 400 | 03 | 08 | 4000308 | 校验码为空 | AUTH_CODE_MISSING | 
| 400 | 03 | 09 | 4000309 | session过期 | AUTH_SESSION_TIMEOUT | 
| 400 | 03 | 10 | 4000310 | 访问登录接口方法错误 | AUTH_METHOD_NOT_AVAILABLE | 
| 400 | 03 | 11 | 4000311 | 账号已在别处登录 | AUTH_SESSION_CONCURRENCY_MAX | 
| 400 | 03 | 12 | 4000312 | 用户账号已锁定 | AUTH_USER_ACCOUNT_LOCKED | 
| 400 | 03 | 13 | 4000313 | 用户账号不可用 | AUTH_USER_ACCOUNT_DISABLED | 
| 400 | 03 | 14 | 4000314 | 用户账号已过期 | AUTH_USER_ACCOUNT_EXPIRED | 
| 400 | 03 | 15 | 4000315 | 用户账号密码过期 | AUTH_USER_CREDENTIALS_EXPIRED | 
| 400 | 03 | 16 | 4000316 | 用户名已存在 | AUTH_USER_USERNAME_REPEAT | 
| 400 | 03 | 17 | 4000317 | 密码错误 | AUTH_PASSWORD_ERROR | 
| 400 | 03 | 18 | 4000318 | qq登录失败 | AUTH_QQ_ERROR | 
| 400 | 04 | 01 | 4000401 | 接口访问频繁 | API_ACCESS_FREQUENT | 
| 400 | 04 | 02 | 4000402 | 重复提交 | API_REPEAT_SUBMIT | 
| 400 | 04 | 03 | 4000403 | 接口访问方法错误 | API_ACCESS_METHOD_ERROR | 
| 400 | 05 | 01 | 4000501 | 检测到xss或sql注入 | SECURITY_CHECK_XSSorSQL | 
| 400 | 10 | 01 | 4001001 | 文章不存在 | ARTICLE_MISSING | 
| 400 | 11 | 01 | 4001101 | 说说不存在 | TALK_MISSING | 
| 400 | 12 | 01 | 4001201 | 分类已存在 | CATEGORY_NAME_REPEAT | 
| 400 | 12 | 02 | 4001202 | 分类不存在 | CATEGORY_MISSING | 
| 400 | 12 | 03 | 4001203 | 分类下存在文章 | CATEGORY_ARTICLE_RELATION | 
| 400 | 13 | 01 | 4001301 | 标签已存在 | TAG_NAME_REPEAT | 
| 400 | 13 | 02 | 4001302 | 标签不存在 | TAG_MISSING | 
| 400 | 13 | 03 | 4001303 | 标签下存在文章 | TAG_ARTICLE_RELATION | 
| 400 | 14 | 01 | 4001401 | 菜单下存在角色 | MENU_ROLE_RELATION | 
| 400 | 15 | 01 | 4001501 | 资源下存在角色 | RESOURCE_ROLE_RELATION | 
| 400 | 99 | 01 | 4009901 | 未知异常 | UNKNOWN_CLIENT_ERROR | 
5xx(server):
| http状态码 | 错误范围 | 细粒度错误 | 业务状态码 | 错误原因 | 错误名称 | 
|---|---|---|---|---|---|
| 500 | 01 | 01 | 5000101 | 参数封装失败 | PARAM_OPERATION_ERROR | 
| 500 | 02 | 01 | 5000201 | 文件上传失败 | FILE_UPLOAD_ERROR | 
| 500 | 02 | 02 | 5000202 | 文件下载失败 | FILE_DOWNLOAD_ERROR | 
| 500 | 04 | 01 | 5000401 | 数据库异常 | SQL_ERROR | 
| 500 | 04 | 02 | 5000402 | redis连接异常 | REDIS_CONNECTION_ERROR | 
| 500 | 99 | 01 | 5009901 | 运行时未知异常 | UNKNOWN_RUNTIME_ERROR | 
| 500 | 99 | 02 | 5009902 | 系统未知异常 | UNKNOWN_SYSTEM_ERROR | 



















