# notebook-cloud
**Repository Path**: tmq777/notebook-cloud
## Basic Information
- **Project Name**: notebook-cloud
- **Description**: notebook-app的后台服务
自用的笔记系统
- **Primary Language**: Java
- **License**: MIT
- **Default Branch**: master
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 1
- **Forks**: 1
- **Created**: 2022-03-19
- **Last Updated**: 2023-02-08
## Categories & Tags
**Categories**: Uncategorized
**Tags**: Java, SpringBoot, SpringCloud
## README
# notebook-cloud
#### 介绍
基于Spring Cloud的笔记本系统**后台服务**。
> [前台工程](https://gitee.com/tmq777/notebook-app)使用vue2.6.14 + element-ui2.15.7搭建
>
> 传送门: [notebook-app](https://gitee.com/tmq777/notebook-app)
>
> 前台界面传送门:[笔记本系统](http://tmq777.gitee.io/notebook-web-app)
#### 开发环境
1. jdk 11.0.10
2. idea
3. mysql 8.0.18
4. redis 6.2.6
5. nacos 1.4.3
6. Spring Cloud 2021
7. Spring Cloud Alibaba 2021.0.1.0
版本管理如下:
```xml
11
2021.0.1
2.2.2
1.0.0
com.alibaba.cloud
spring-cloud-alibaba-dependencies
2021.0.1.0
pom
import
org.springframework.cloud
spring-cloud-dependencies
${spring-cloud.version}
pom
import
org.mybatis.spring.boot
mybatis-spring-boot-starter
${mybatis.version}
```
#### 开发环境部署说明
1. 服务器只暴露file模块端口(最终静态资源使用gitee pages服务托管)
1. 开发时前端使用本地调试
2. 服务器上需要有nacos作为注册中心,目前默认端口18848
3. 服务器上需要有mysql服务,目前默认端口13306
4. 服务器上需要有redis服务,目前使用了redis集群,端口为7001~7006
5. 前台js的baseUrl需要改成服务器的ip
`src\util\ApiUrls.js`
```javascript
const apis = {
api_article: api_article,
api_auth: api_auth,
api_file: api_file,
baseHost: "http://localhost:9000", // 修改ip-网关地址
}
```
6. 所有服务的数据源配置使用了nacos配置中心进行配置,在配置中心的namespace为notebook,group为common
7. 后台服务需要配置对应的跨域请求允许策略
1. 认证中心`cn/t/notebook/auth/config/SecurityConfig.java`
1. `corsFilter`方法中配置允许的前台请求(认证中心有部分服务网关不做过滤,所以需要单独配置)
2. 网关`cn/t/notebook/gateway/config/GatewayConfig.java`
1. `corsFilter`方法中配置允许的前台请求
> cors允许的跨域配置在nacos中,dataId为cors.yaml
8. 本工程部署时将前台打包后的dist目录中所有文件放到了`file`模块的`static`目录下,以file模块作为服务器。同时,`file`模块配置了404错误页面-指向`static`下的index.html,防止刷新后找不到路由页面了。
> 最终部署时使用了gitee pages服务托管前台文件,不再使用file模块部署了
>
> gitee pages提供了单页面刷新的能力,只需要在根目录新建.spa文件即可,不用输入任何内容,即可完成静态刷新并保持路由的功能
```java
@Configuration
public class RefreshPageConfig implements ErrorPageRegistrar {
@Override
public void registerErrorPages(ErrorPageRegistry registry) {
/*错误类型为404,找不到网页的,默认显示404.html网页*/
ErrorPage e404 = new ErrorPage(HttpStatus.NOT_FOUND, "/index.html");
registry.addErrorPages(e404);
}
}
```
> 由于vue使用的前台history路由,file模块中不会有相应的url路径映射,刷新时会被跳转到404页面,所以需要配置404显示地址,将其引导回vue的index让其路由生效。
#### docker部署说明(最终部署)
**此处注意最终的后台服务器需要开放 网关端口(9000),所有的服务都是通过网关转发的**
**后台服务只需要暴露网关端口9000即可,该端口使用免费的内网穿透(目前使用的是云服务器公网IP+自行搭建frp内网穿透)进行映射**
**前台页面部署在云服务器上,或者可以通过另一条隧道映射**
> 最终前台页面部署时使用了gitee pages服务托管前台文件
>
> http://tmq777.gitee.io/notebook-web-app
>
> 每次更新静态文件后重新部署即可
**注意前台页面所在的ip域名需要被后台允许跨域,所有的有跨域配置的服务都需要进行配置**
**Docker部署时很占用内存,如果出现部署失败,但是本地却是好的,可能是虚拟机内存不够了,需要扩展**
1. 需要部署到docker上的服务,resource下都会建立Dockerfile
2. 在idea中编辑docker配置、选择Dockerfile配置
3. 选择Dockerfile路径、指定Context folder为 projectName/target
> 例如, auth服务的Context folder为 auth/target
>
> 因为dockerfile构建时根据相对路径构建,打包后的Dockerfile位于target下
4. 指定image tag和Container name
5. 指定Bind ports
6. 指定启动参数 --net=host
> 在Run options处指定以宿主机网络映射启动,容器间可以访问
>
> 否则服务起来后可能访问不到redis/mysql
7. 最后添加maven命令,编译打包
`clean package -Dmaven.test.skip=true`
8. 截图如下

9. docker插件如下
```XML
com.spotify
docker-maven-plugin
1.0.0
http://localhost:12375
${docker.image.prefix}/${project.artifactId}
src/main/resource/docker
/
${project.build.directory}
${project.build.finalName}.jar
```
> ${docker.image.prefix}自己在properties标签内指定
10. 运行配置好的docker配置即可远程推送到docker服务器
---
#### 系统关键点说明
1. 用户每次登录后,在保持当前系统不退出的情况下,在24小时内不会出现超时,超过24小时或者中途关闭了浏览器或当前`tab`,则需要重新登录
2. 笔记编辑器取消了图片上传的功能,原因是本系统主要用户`markdown`文档的同步和备份,如果md中有自定义的图片可能会导致不一致,当前,可公开访问的图片链接除外。
> 该功能重新打包编译了第三方依赖包,编译后的文件和前台代码在同一目录,使用时解压覆盖依赖即可
---
#### 遇到的问题
1. 使用nacos作为注册中心时(版本如上文所述), `SpringCloudGateway`可能会出现`//lb`服务转发失败报503的问题,原因如下
因为 `ReactiveLoadBalancerClientFilter` 全局过滤器没有加载
`ReactiveLoadBalancerClientFilter` 是否会加载取决于 `GatewayReactiveLoadBalancerClientAutoConfiguration` 配置类中的条件,如下
```java
@Bean
@ConditionalOnBean(LoadBalancerClientFactory.class)
@ConditionalOnMissingBean(ReactiveLoadBalancerClientFilter.class)
@ConditionalOnEnabledGlobalFilter
public ReactiveLoadBalancerClientFilter gatewayLoadBalancerClientFilter(LoadBalancerClientFactory clientFactory,
GatewayLoadBalancerProperties properties, LoadBalancerProperties loadBalancerProperties) {
return new ReactiveLoadBalancerClientFilter(clientFactory, properties, loadBalancerProperties);
}
```
上述源代码中类 `LoadBalancerClientFactory` 是包 `spring-cloud-loadbalancer` 的,所以如果缺少则不会注入过滤器 `ReactiveLoadBalancerClientFilter`。
引入相关依赖即可
```xml
org.springframework.cloud
spring-cloud-loadbalancer
```
2. 使用`druid`数据源时`maxWait`属性不能配置太大,否则会引起长时间等待导致服务接口不可用
3. 做一对多查询时,由于`mybatis`会在数据查询完成后进行实体映射,此时如果限制了limit分页,那么当前页一旦有重复数据后,最终实体映射数据会少于limit 数量,解决办法是先保证需要的数据不重复,然后主表再连接关联表进行查询,如下:
```sql
select
a.id, a.create_time, a.update_time, a.title, a.locked, a.category_name, b.note_id, b.label_name
from (select
s1.id,
DATE_FORMAT( s1.create_time, '%Y/%m/%d %H:%i' ) AS create_time,
DATE_FORMAT( s1.update_time, '%Y/%m/%d %H:%i' ) AS update_time,
s1.title,
s1.locked,
s2.category_name
from
notes s1 inner join categories s2 ON s1.category_id = s2.category_id
where s1.user_id = s1.user_id = #{userId, jdbcType=INTEGER} limit #{pageIndex, jdbcType=INTEGER}, 10) as a
left join labels b ON a.id = b.note_id
```
> 直接查询主表然后限制分页数据,查询完的结果集作为主表再次连接关联表,最终查询出的数据可能会大于10条(limit 值),但是经过mybatis映射后最终会变成10条
4. 部署在真正服务器上时,注意从网关开始到各个服务的跨域设置都要`addAllowedOriginPattern`,添加的值为服务器前端代码所在的ip和端口
5. springboot打包后如果需要读取classpath路径下的文件,则需要改为使用流读取,否则可能会找不到文件
```java
// 注意要添加"/"前缀才会从类路径开始,否则读取的是相对路径
this.getClass().getResourceAsStream("/China-City-List-latest.csv")
```
6. 读取yml中配置的数组需要使用对象来接收
```java
@Bean
@ConfigurationProperties(prefix = "notebook.cors")
public ListPropertiesContainerListPropertiesContainer
// ListPropertiesContainer对象中有一个properties属性,List类型
return new ListPropertiesContainer();
}
```