Compare commits

..

43 Commits

Author SHA1 Message Date
禾几海
9d462cc876 feat: 拆分为数据库地址 2020-11-22 23:03:37 +08:00
禾几海
ec96a787ef fix(DruidConfig): validationQuery not set error 2020-11-14 21:24:23 +08:00
禾几海
91de56cb32 feat: 参数校验 2020-10-23 17:09:10 +08:00
禾几海
e2a3fb6a6c refactor: 将实现逻辑放到service中 2020-10-23 16:33:29 +08:00
禾几海
49ff1865bb feat: 安装页面布局 loading动画 2020-10-22 15:41:34 +08:00
禾几海
b920034ad6 feat: 安装页面布局 2020-10-22 14:12:01 +08:00
禾几海
76f3d16e09 feat: 安装页面 2020-10-22 00:21:23 +08:00
禾几海
9dafc6d5a7 feat: 安装功能 2020-10-18 19:49:19 +08:00
禾几海
ec693da079 fix: 设置为共有访问 2020-10-18 13:57:39 +08:00
禾几海
58498ef225 fix: 测试环境仍然使用test环境中的数据库连接 并在单元测试中mock profiles 2020-10-18 13:56:20 +08:00
禾几海
c56a3eaf83 fix: 修复测试完成后产生的测试配置文件无法删除的异常 2020-10-18 13:20:10 +08:00
禾几海
e0abfb7d70 feat: 存储博客的默认数据存储路径 2020-10-18 13:19:25 +08:00
禾几海
96cb2dcee4 feat: 启动时首选本地文件中数据库的配置进行加载 2020-10-18 11:38:05 +08:00
禾几海
e39763ad0c style: rename 'schema_h2' to 'schema-h2' 2020-10-18 11:36:12 +08:00
禾几海
a3edc00a03 feat: 从配置文件中初始化配置 2020-10-17 00:13:17 +08:00
禾几海
c0687b4776 refactor: 修改FileResponse属性bucket为type 2020-10-17 00:12:37 +08:00
禾几海
b3b3a7a908 refactor: 添加修改配置项 2020-10-16 23:30:59 +08:00
禾几海
56efdc44d7 fix: 由配置信息来初始化对象 2020-10-16 23:28:24 +08:00
禾几海
e4684e6150 fix: 配置修改后不生效的异常 2020-10-16 22:49:17 +08:00
禾几海
65d65221e8 feat: 配置管理接口 2020-10-16 22:41:55 +08:00
禾几海
47df223655 test: fileService的mock 2020-10-16 19:01:17 +08:00
禾几海
fa95f2f69e test: fileManager测试 2020-10-16 18:53:51 +08:00
禾几海
5598804ddc feat: 本地文件存储 2020-10-16 18:05:26 +08:00
禾几海
ed7d18d491 feat: 项目启动注入配置表中配置到内存中 2020-10-16 16:36:05 +08:00
禾几海
2fbe030da9 feat: 添加配置存储表及其处理类 2020-10-16 16:35:28 +08:00
禾几海
5f3cbece7b feat: 本地文件服务 2020-10-16 15:16:17 +08:00
禾几海
4942daf900 ci: 调整配置文件 2020-10-16 14:35:50 +08:00
禾几海
9f070169b4 ci: 调整配置文件 2020-10-16 14:30:58 +08:00
禾几海
4fa114eb1e test: 完善测试例 2020-10-16 14:30:35 +08:00
禾几海
431ce8ac28 feat: 修复空值异常,完善删除接口功能 2020-10-16 14:29:39 +08:00
禾几海
49000e9ee6 feat: 删除文件 2020-10-15 23:30:19 +08:00
禾几海
19068ff14d refactor(文件服务): 接口修改返回值 2020-10-15 22:46:55 +08:00
禾几海
3d0253a35a refactor(文件服务): 修改类文件包路径 2020-10-15 22:07:48 +08:00
禾几海
0283bd11dc fix(文件服务): 修复无法获取到文件服务的异常 2020-10-15 19:45:43 +08:00
禾几海
60347c7629 refactor: 重命名QiniuService ->FileService
重命名为FileService 并修改方法为获取File Manager来处理文件
2020-10-15 18:29:52 +08:00
禾几海
8de0cbded8 feat(resources): 新增简易部署配置文件 2020-10-15 18:02:53 +08:00
禾几海
0ba7e06695 Merge pull request #12 from xiaohai2271/dependabot/maven/junit-junit-4.13.1
build(deps): bump junit from 4.12 to 4.13.1
2020-10-14 07:56:21 +08:00
dependabot[bot]
512d04f3e9 build(deps): bump junit from 4.12 to 4.13.1
Bumps [junit](https://github.com/junit-team/junit4) from 4.12 to 4.13.1.
- [Release notes](https://github.com/junit-team/junit4/releases)
- [Changelog](https://github.com/junit-team/junit4/blob/main/doc/ReleaseNotes4.12.md)
- [Commits](https://github.com/junit-team/junit4/compare/r4.12...r4.13.1)

Signed-off-by: dependabot[bot] <support@github.com>
2020-10-13 17:49:19 +00:00
禾几海
ada8f67171 Merge remote-tracking branch 'origin/master' 2020-10-09 18:57:42 +08:00
禾几海
6e12331e61 feat: 添加文件上传接口 2020-10-09 18:56:59 +08:00
禾几海
81ee71adf1 Create codeql-analysis.yml 2020-10-03 18:02:48 +08:00
禾几海
7e7332694e docs: typo 2020-09-07 12:28:07 +08:00
禾几海
8db690a55f Update issue templates 2020-09-07 12:23:44 +08:00
48 changed files with 2331 additions and 226 deletions

38
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View File

@@ -0,0 +1,38 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: ''
assignees: ''
---
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Desktop (please complete the following information):**
- OS: [e.g. iOS]
- Browser [e.g. chrome, safari]
- Version [e.g. 22]
**Smartphone (please complete the following information):**
- Device: [e.g. iPhone6]
- OS: [e.g. iOS8.1]
- Browser [e.g. stock browser, safari]
- Version [e.g. 22]
**Additional context**
Add any other context about the problem here.

View File

@@ -14,6 +14,8 @@ jobs:
runs-on: ubuntu-latest
env:
KEY: ${{ secrets.WEB_HOOK_ACCESS_KEY }}
QINIU_ACCESSKEY: ${{ secrets.QINIU_ACCESSKEY }}
QINIU_SECRETKEY: ${{ secrets.QINIU_SECRETKEY }}
steps:
- uses: actions/checkout@v2

71
.github/workflows/codeql-analysis.yml vendored Normal file
View File

@@ -0,0 +1,71 @@
# For most projects, this workflow file will not need changing; you simply need
# to commit it to your repository.
#
# You may wish to alter this file to override the set of languages analyzed,
# or to provide custom queries or build logic.
name: "CodeQL"
on:
push:
branches: [master]
pull_request:
# The branches below must be a subset of the branches above
branches: [master]
schedule:
- cron: '0 14 * * 2'
jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
# Override automatic language detection by changing the below list
# Supported options are ['csharp', 'cpp', 'go', 'java', 'javascript', 'python']
language: ['java']
# Learn more...
# https://docs.github.com/en/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#overriding-automatic-language-detection
steps:
- name: Checkout repository
uses: actions/checkout@v2
with:
# We must fetch at least the immediate parents so that if this is
# a pull request then we can checkout the head.
fetch-depth: 2
# If this run was triggered by a pull request event, then checkout
# the head of the pull request instead of the merge commit.
- run: git checkout HEAD^2
if: ${{ github.event_name == 'pull_request' }}
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v1
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
# By default, queries listed here will override any specified in a config file.
# Prefix the list here with "+" to use these queries and those in the config file.
# queries: ./path/to/local/query, your-org/your-repo/queries@main
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@v1
# Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
# and modify them (or add more) to build your code if your project
# uses a compiled language
#- run: |
# make bootstrap
# make release
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v1

View File

@@ -12,9 +12,9 @@ jobs:
build:
runs-on: ubuntu-latest
# env:
# APPLICATION_PROPERTIES_TEST: ${{ secrets.APPLICATION_PROPERTIES_TEST }}
# APPLICATION_PROPERTIES_PROD: ${{ secrets.APPLICATION_PROPERTIES_PROD }}
env:
QINIU_ACCESSKEY: ${{ secrets.QINIU_ACCESSKEY }}
QINIU_SECRETKEY: ${{ secrets.QINIU_SECRETKEY }}
steps:
- uses: actions/checkout@v2

View File

@@ -1411,6 +1411,78 @@
| 401 | Unauthorized ||
| 403 | Forbidden ||
| 404 | Not Found ||
## fileUpload
**接口描述**:
**接口地址**:`/fileUpload`
**请求方式**`POST`
**consumes**:`["application/json"]`
**produces**:`["*/*"]`
**请求参数**
| 参数名称 | 参数说明 | in | 是否必须 | 数据类型 | schema |
| ------------ | -------------------------------- |-----------|--------|----|--- |
|file[]| file[] | formData | true |array | file |
**响应示例**:
```json
{
"code": 0,
"msg": "",
"result": [
{}
]
}
```
**响应参数**:
| 参数名称 | 参数说明 | 类型 | schema |
| ------------ | -------------------|-------|----------- |
|code| |integer(int32) | integer(int32) |
|msg| |string | |
|result| |array | Map«string,object» |
**schema属性说明**
**Map«string,object»**
| 参数名称 | 参数说明 | 类型 | schema |
| ------------ | ------------------|--------|----------- |
**响应状态**:
| 状态码 | 说明 | schema |
| ------------ | -------------------------------- |---------------------- |
| 200 | OK |Response«List«Map«string,object»»»|
| 201 | Created ||
| 401 | Unauthorized ||
| 403 | Forbidden ||
| 404 | Not Found ||
## headerInfo
@@ -1623,6 +1695,231 @@
| 401 | Unauthorized ||
| 403 | Forbidden ||
| 404 | Not Found ||
# config-controller
## getConfiguration
**接口描述**:
**接口地址**:`/admin/config`
**请求方式**`GET`
**consumes**:``
**produces**:`["*/*"]`
**请求参数**
暂无
**响应示例**:
```json
{
"code": 0,
"msg": "",
"result": [
{
"id": 0,
"name": "",
"value": ""
}
]
}
```
**响应参数**:
| 参数名称 | 参数说明 | 类型 | schema |
| ------------ | -------------------|-------|----------- |
|code| |integer(int32) | integer(int32) |
|msg| |string | |
|result| |array | Config |
**schema属性说明**
**Config**
| 参数名称 | 参数说明 | 类型 | schema |
| ------------ | ------------------|--------|----------- |
|id | |integer(int32) | |
|name | |string | |
|value | |string | |
**响应状态**:
| 状态码 | 说明 | schema |
| ------------ | -------------------------------- |---------------------- |
| 200 | OK |Response«List«Config»»|
| 401 | Unauthorized ||
| 403 | Forbidden ||
| 404 | Not Found ||
## addConfiguration
**接口描述**:
**接口地址**:`/admin/config`
**请求方式**`POST`
**consumes**:`["application/json"]`
**produces**:`["*/*"]`
**请求参数**
| 参数名称 | 参数说明 | in | 是否必须 | 数据类型 | schema |
| ------------ | -------------------------------- |-----------|--------|----|--- |
|configs| configs | query | false |array | Config |
**响应示例**:
```json
{
"code": 0,
"msg": "",
"result": [
{
"id": 0,
"name": "",
"value": ""
}
]
}
```
**响应参数**:
| 参数名称 | 参数说明 | 类型 | schema |
| ------------ | -------------------|-------|----------- |
|code| |integer(int32) | integer(int32) |
|msg| |string | |
|result| |array | Config |
**schema属性说明**
**Config**
| 参数名称 | 参数说明 | 类型 | schema |
| ------------ | ------------------|--------|----------- |
|id | |integer(int32) | |
|name | |string | |
|value | |string | |
**响应状态**:
| 状态码 | 说明 | schema |
| ------------ | -------------------------------- |---------------------- |
| 200 | OK |Response«List«Config»»|
| 201 | Created ||
| 401 | Unauthorized ||
| 403 | Forbidden ||
| 404 | Not Found ||
## updateConfiguration
**接口描述**:
**接口地址**:`/admin/config`
**请求方式**`PUT`
**consumes**:`["application/json"]`
**produces**:`["*/*"]`
**请求参数**
| 参数名称 | 参数说明 | in | 是否必须 | 数据类型 | schema |
| ------------ | -------------------------------- |-----------|--------|----|--- |
|configs| configs | query | false |array | Config |
**响应示例**:
```json
{
"code": 0,
"msg": "",
"result": [
{
"id": 0,
"name": "",
"value": ""
}
]
}
```
**响应参数**:
| 参数名称 | 参数说明 | 类型 | schema |
| ------------ | -------------------|-------|----------- |
|code| |integer(int32) | integer(int32) |
|msg| |string | |
|result| |array | Config |
**schema属性说明**
**Config**
| 参数名称 | 参数说明 | 类型 | schema |
| ------------ | ------------------|--------|----------- |
|id | |integer(int32) | |
|name | |string | |
|value | |string | |
**响应状态**:
| 状态码 | 说明 | schema |
| ------------ | -------------------------------- |---------------------- |
| 200 | OK |Response«List«Config»»|
| 201 | Created ||
| 401 | Unauthorized ||
| 403 | Forbidden ||
| 404 | Not Found ||
# links-controller
## all
@@ -1649,8 +1946,8 @@
| 参数名称 | 参数说明 | in | 是否必须 | 数据类型 | schema |
| ------------ | -------------------------------- |-----------|--------|----|--- |
|count| count | query | true |integer | |
|page| page | query | true |integer | |
|deleted| deleted | query | false |boolean | |
|page| page | query | true |integer | |
**响应示例**:

View File

@@ -9,7 +9,8 @@
### 2. 拉取项目到本地
``` shell script
git clone https://github.com/xiaohai2271/blog-backEnd.git
git clone git@github.com:xiaohai2271/blog-backEnd.git
#
git clone git@github.com:xiaohai2271/blog-backEnd.git
```
### 3. maven构建
@@ -26,7 +27,7 @@ java -jar target/blog-0.0.1-SNAPSHOT.jar
```shell script
mvn clean # 清理项目资源
mvn clean package # 清理项目资源并重新打包
mvn clean package -DskipTest # 清理项目资源,重新打包并跳过测试
mvn clean package -DskipTests # 清理项目资源,重新打包并跳过测试
mvn test # 运行测试
..... #待添加
```

View File

@@ -131,7 +131,7 @@
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<version>4.13.1</version>
</dependency>
<!-- JJwt -->

View File

@@ -1,20 +1,34 @@
package cn.celess.blog;
import org.mybatis.spring.annotation.MapperScan;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.scheduling.annotation.EnableAsync;
/**
* @author zheng
*/
@SpringBootApplication
@EnableAsync
@MapperScan("cn.celess.blog.mapper")
public class BlogApplication {
public static final Logger logger = LoggerFactory.getLogger(BlogApplication.class);
public static ConfigurableApplicationContext context;
public static void main(String[] args) {
SpringApplication.run(BlogApplication.class, args);
logger.info("启动完成!");
context = SpringApplication.run(BlogApplication.class, args);
}
public static void restart() {
ApplicationArguments args = context.getBean(ApplicationArguments.class);
Thread thread = new Thread(() -> {
context.close();
context = SpringApplication.run(BlogApplication.class, args.getSourceArgs());
});
thread.setDaemon(false);
thread.start();
}
}

View File

@@ -0,0 +1,59 @@
package cn.celess.blog.configuration;
import cn.celess.blog.enmu.ConfigKeyEnum;
import cn.celess.blog.entity.Config;
import cn.celess.blog.mapper.ConfigMapper;
import cn.celess.blog.service.fileserviceimpl.LocalFileServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component;
import java.io.File;
import java.util.List;
import java.util.stream.Collectors;
/**
* @author : xiaohai
* @date : 2020/10/16 16:00
* @desc :
*/
@Component
@Slf4j
public class ApplicationListener implements ApplicationRunner {
@Autowired
ConfigMapper configMapper;
@Autowired
Environment env;
@Override
public void run(ApplicationArguments args) throws Exception {
log.info("博客启动!");
// 设置初始值
setProperty(ConfigKeyEnum.FILE_QINIU_SECRET_KEY);
setProperty(ConfigKeyEnum.FILE_QINIU_ACCESS_KEY);
setProperty(ConfigKeyEnum.FILE_QINIU_BUCKET);
setProperty(ConfigKeyEnum.BLOG_FILE_PATH);
List<Config> configurations = configMapper.getConfigurations()
.stream()
.filter(config -> config.getValue() != null && !"".equals(config.getValue()))
.collect(Collectors.toList());
configurations.forEach(config -> System.setProperty(config.getName(), config.getValue()));
log.debug("注入配置成功 {}", configurations.stream().map(Config::getName).collect(Collectors.toList()));
File path = new File(LocalFileServiceImpl.getPath(System.getProperty(ConfigKeyEnum.BLOG_FILE_PATH.getKey())));
if (!path.exists() && !path.mkdirs()) {
throw new IllegalAccessException("创建数据目录失败==>" + path.getAbsolutePath());
}
}
private void setProperty(ConfigKeyEnum e) {
String property = env.getProperty(e.getKey());
if (property != null) {
System.setProperty(e.getKey(), property);
}
}
}

View File

@@ -1,41 +1,81 @@
package cn.celess.blog.configuration;
import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.beans.factory.annotation.Value;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.Arrays;
import java.util.Properties;
/**
* @author : xiaohai
* @date : 2019/03/28 14:26
*/
@Slf4j
@Configuration
public class DruidConfig {
@Value("${spring.datasource.url}")
private String dbUrl;
@Value("${spring.datasource.username}")
private String username;
@Autowired
Environment env;
@Value("${spring.datasource.password}")
private String password;
@Value("${spring.datasource.driver-class-name}")
private String driverClassName;
public static final String DB_CONFIG_PATH = System.getProperty("user.home") + "/blog/application.properties";
public static final String DB_CONFIG_URL_PREFIX = "spring.datasource.url";
public static final String DB_CONFIG_USERNAME_PREFIX = "spring.datasource.username";
public static final String DB_CONFIG_PASSWORD_PREFIX = "spring.datasource.password";
public static final String DB_CONFIG_DRIVER_CLASS_NAME_PREFIX = "spring.datasource.driver-class-name";
public static final String TEST_PROFILES = "test";
@Bean
public DruidDataSource druidDataSource() {
DruidDataSource dataSource = new DruidDataSource();
dataSource.setDriverClassName(driverClassName);
// 数据库基本信息
dataSource.setUrl(dbUrl);
dataSource.setUsername(username);
dataSource.setPassword(password);
// 数据库连接池配置
public DruidDataSource initDataSource() throws IOException {
DruidDataSource dataSource;
File file = new File(DB_CONFIG_PATH);
if (file.exists() && !Arrays.asList(env.getActiveProfiles()).contains(TEST_PROFILES)) {
log.debug("从文件中获取数据库配置");
dataSource = readConfigFromFile(file);
} else {
log.debug("默认数据库配置");
dataSource = defaultDruidSource();
}
dataSource.setInitialSize(10);
dataSource.setMinIdle(10);
dataSource.setMaxActive(100);
dataSource.setValidationQuery("select 1");
return dataSource;
}
private DruidDataSource readConfigFromFile(File file) throws IOException {
Properties properties = new Properties();
FileInputStream fis = new FileInputStream(file);
properties.load(fis);
fis.close();
String url = properties.getProperty(DB_CONFIG_URL_PREFIX, null);
String username = properties.getProperty(DB_CONFIG_USERNAME_PREFIX, null);
String password = properties.getProperty(DB_CONFIG_PASSWORD_PREFIX, null);
String className = properties.getProperty(DB_CONFIG_DRIVER_CLASS_NAME_PREFIX, null);
if (url == null || username == null || password == null || className == null) {
return defaultDruidSource();
}
DruidDataSource dataSource = new DruidDataSource();
dataSource.setDriverClassName(className);
dataSource.setUrl(url);
dataSource.setUsername(username);
dataSource.setPassword(password);
return dataSource;
}
private DruidDataSource defaultDruidSource() {
DruidDataSource dataSource = new DruidDataSource();
dataSource.setDriverClassName(env.getProperty(DruidConfig.DB_CONFIG_DRIVER_CLASS_NAME_PREFIX));
dataSource.setUrl(env.getProperty(DruidConfig.DB_CONFIG_URL_PREFIX));
dataSource.setUsername(env.getProperty(DruidConfig.DB_CONFIG_USERNAME_PREFIX));
dataSource.setPassword(env.getProperty(DruidConfig.DB_CONFIG_PASSWORD_PREFIX));
return dataSource;
}
}

View File

@@ -2,11 +2,12 @@ package cn.celess.blog.controller;
import cn.celess.blog.enmu.ResponseEnum;
import cn.celess.blog.entity.Response;
import cn.celess.blog.entity.model.QiniuResponse;
import cn.celess.blog.entity.model.FileResponse;
import cn.celess.blog.exception.MyException;
import cn.celess.blog.service.CountService;
import cn.celess.blog.service.QiniuService;
import cn.celess.blog.service.FileService;
import cn.celess.blog.util.HttpUtil;
import cn.celess.blog.util.RedisUserUtil;
import cn.celess.blog.util.RedisUtil;
import cn.celess.blog.util.VeriCodeUtil;
import com.fasterxml.jackson.databind.JsonNode;
@@ -27,9 +28,7 @@ import javax.servlet.http.HttpServletResponse;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.*;
import java.util.concurrent.TimeUnit;
/**
@@ -43,11 +42,11 @@ public class CommonController {
@Autowired
CountService countService;
@Autowired
QiniuService qiniuService;
FileService fileService;
@Autowired
RedisUtil redisUtil;
@Autowired
HttpServletRequest request;
RedisUserUtil redisUserUtil;
@GetMapping("/counts")
@@ -89,7 +88,7 @@ public class CommonController {
* @throws IOException IOException
*/
@GetMapping(value = "/imgCode", produces = MediaType.IMAGE_PNG_VALUE)
public void getImg(HttpServletResponse response) throws IOException {
public void getImg(HttpServletRequest request, HttpServletResponse response) throws IOException {
Object[] obj = VeriCodeUtil.createImage();
request.getSession().setAttribute("code", obj[0]);
//将图片输出给浏览器
@@ -133,9 +132,10 @@ public class CommonController {
* FIXME :: 单张图片多次上传的问题
* editor.md图片上传的接口
* FUCK !!!
*
* !! 推荐使用 fileUpload(/fileUpload) 接口
* @param file 文件
*/
@Deprecated
@PostMapping("/imgUpload")
public void upload(HttpServletRequest request, HttpServletResponse response, @RequestParam("editormd-image-file") MultipartFile file) throws IOException {
Map<String, Object> map = new HashMap<>();
@@ -161,10 +161,10 @@ public class CommonController {
String mime = fileName.substring(fileName.lastIndexOf("."));
if (".png".equals(mime.toLowerCase()) || ".jpg".equals(mime.toLowerCase()) ||
".jpeg".equals(mime.toLowerCase()) || ".bmp".equals(mime.toLowerCase())) {
QiniuResponse qiniuResponse = qiniuService.uploadFile(file.getInputStream(), "img_" + System.currentTimeMillis() + mime);
FileResponse fileResponse = fileService.getFileManager().uploadFile(file.getInputStream(), "img_" + System.currentTimeMillis() + mime);
map.put("success", 1);
map.put("message", "上传成功");
map.put("url", "http://cdn.celess.cn/" + qiniuResponse.key);
map.put("url", "http://cdn.celess.cn/" + fileResponse.key);
response.getWriter().println(mapper.writeValueAsString(map));
redisUtil.setEx(request.getRemoteAddr() + "-ImgUploadTimes", uploadTimes + 1 + "", 2, TimeUnit.HOURS);
return;
@@ -186,4 +186,37 @@ public class CommonController {
JsonNode images = root.get("images").elements().next();
return Response.success("https://cn.bing.com" + images.get("url").asText());
}
@PostMapping("/fileUpload")
public Response<List<Map<String, Object>>> fileUpload(@RequestParam("file[]") MultipartFile[] files) throws IOException {
List<Map<String, Object>> result = new ArrayList<>();
String uploadTimesStr = redisUtil.get(redisUserUtil.get().getEmail() + "-fileUploadTimes");
int uploadTimes = 0;
if (uploadTimesStr != null) {
uploadTimes = Integer.parseInt(uploadTimesStr);
}
if (uploadTimes == 10) {
throw new MyException(ResponseEnum.FAILURE.getCode(), "上传次数已达10次请2小时后在上传");
}
if (files.length == 0) {
throw new MyException(ResponseEnum.NO_FILE);
}
for (MultipartFile file : files) {
Map<String, Object> resp = new HashMap<>(4);
String fileName = file.getOriginalFilename();
assert fileName != null;
String mime = fileName.substring(fileName.lastIndexOf("."));
String name = fileName.replace(mime, "").replaceAll(" ", "");
FileResponse fileResponse = fileService.getFileManager().uploadFile(file.getInputStream(), "file_" + name + '_' + System.currentTimeMillis() + mime);
resp.put("originalFilename", fileName);
resp.put("success", fileResponse != null);
if (fileResponse != null) {
resp.put("host", "http://cdn.celess.cn/");
resp.put("path", fileResponse.key);
}
result.add(resp);
}
return Response.success(result);
}
}

View File

@@ -0,0 +1,39 @@
package cn.celess.blog.controller;
import cn.celess.blog.entity.Config;
import cn.celess.blog.entity.Response;
import cn.celess.blog.mapper.ConfigMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
* @author : xiaohai
* @date : 2020/10/16 20:56
* @desc : 配置管理接口
*/
@RestController
public class ConfigController {
@Autowired
ConfigMapper configMapper;
@GetMapping("/admin/config")
public Response<List<Config>> getConfiguration() {
return Response.success(configMapper.getConfigurations());
}
@PutMapping("/admin/config")
public Response<List<Config>> updateConfiguration(@RequestBody List<Config> configs) {
configs.forEach(config -> configMapper.updateConfiguration(config));
configs.forEach(config -> System.setProperty(config.getName(), config.getValue()));
return Response.success(configMapper.getConfigurations());
}
@PostMapping("/admin/config")
public Response<List<Config>> addConfiguration(@RequestBody List<Config> configs) {
configs.forEach(config -> configMapper.addConfiguration(config));
configs.forEach(config -> System.setProperty(config.getName(), config.getValue()));
return Response.success(configMapper.getConfigurations());
}
}

View File

@@ -0,0 +1,78 @@
package cn.celess.blog.controller;
import cn.celess.blog.BlogApplication;
import cn.celess.blog.enmu.ConfigKeyEnum;
import cn.celess.blog.enmu.ResponseEnum;
import cn.celess.blog.entity.Config;
import cn.celess.blog.entity.InstallParam;
import cn.celess.blog.entity.Response;
import cn.celess.blog.exception.MyException;
import cn.celess.blog.mapper.ConfigMapper;
import cn.celess.blog.service.InstallService;
import cn.celess.blog.util.RegexUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.validation.Valid;
import java.util.Map;
/**
* @author : xiaohai
* @date : 2020/10/18 15:36
* @desc :
*/
@Slf4j
@Controller
@Validated
public class InstallController {
@Autowired
InstallService installService;
@Autowired
ConfigMapper configMapper;
@GetMapping("/is_install")
@ResponseBody
public Response<Map<String, Object>> isInstall() {
Map<String, Object> install = installService.isInstall();
return Response.success(install);
}
@PostMapping("/install")
@ResponseBody
public Response<Object> install(@Valid @RequestBody InstallParam installParam) {
if (!RegexUtil.emailMatch(installParam.getEmail())) {
throw new MyException(ResponseEnum.PARAMETERS_EMAIL_ERROR);
}
if (!RegexUtil.pwdMatch(installParam.getPassword())) {
throw new MyException(ResponseEnum.PARAMETERS_PWD_ERROR);
}
return Response.success(installService.install(installParam));
}
@GetMapping("/default_config")
@ResponseBody
public Response<String> defaultConfig() {
return null;
}
@GetMapping("/install")
public String install() {
Config configuration = configMapper.getConfiguration(ConfigKeyEnum.BLOG_INSTALLED.getKey());
if (Boolean.parseBoolean(configuration.getValue())) {
return "index.html";
}
log.info("博客第一次运行,还未安装");
return "install.html";
}
}

View File

@@ -0,0 +1,37 @@
package cn.celess.blog.enmu;
/**
* @author : xiaohai
* @date : 2020/10/16 16:41
* @desc :
*/
public enum ConfigKeyEnum {
/**
* 枚举
*/
FILE_TYPE("file.type"),
FILE_QINIU_ACCESS_KEY("file.qiniu.accessKey"),
FILE_QINIU_SECRET_KEY("file.qiniu.secretKey"),
FILE_QINIU_BUCKET("file.qiniu.bucket"),
FILE_LOCAL_DIRECTORY_PATH("file.local.directoryPath"),
BLOG_FILE_PATH("blog.file.path"),
BLOG_INSTALLED("blog.installed"),
DB_TYPE("db.type"),
DB_URL("spring.datasource.url"),
DB_USERNAME("spring.datasource.username"),
DB_PASSWORD("spring.datasource.password"),
DB_DRIVER_CLASS_NAME("spring.datasource.driver-class-name"),
BLOG_DB_PATH("blog.db.path");
private final String key;
ConfigKeyEnum(String key) {
this.key = key;
}
public String getKey() {
return key;
}
}

View File

@@ -62,6 +62,8 @@ public enum ResponseEnum {
APPLY_LINK_NO_ADD_THIS_SITE(7200, "暂未在您的网站中抓取到本站链接"),
DATA_EXPIRED(7300, "数据过期"),
CANNOT_GET_DATA(7400, "暂无法获取到数据"),
NO_FILE(7500, "未选择文件,请重新选择"),
//提交更新之前,没有获取数据/,
DID_NOT_GET_THE_DATA(8020, "非法访问"),

View File

@@ -0,0 +1,26 @@
package cn.celess.blog.entity;
import cn.celess.blog.enmu.ConfigKeyEnum;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* @author : xiaohai
* @date : 2020/10/16 15:24
* @desc :
*/
@Data
@NoArgsConstructor
public class Config {
private Integer id;
private String name;
private String value;
public Config(String name) {
this.name = name;
}
public Config(ConfigKeyEnum e) {
this.name = e.getKey();
}
}

View File

@@ -0,0 +1,38 @@
package cn.celess.blog.entity;
import lombok.Data;
import javax.validation.constraints.NotBlank;
/**
* @author : xiaohai
* @date : 2020/10/18 14:52
* @desc :
*/
@Data
public class InstallParam {
@NotBlank(message = "数据库类型不可为空")
private String dbType;
@NotBlank(message = "数据库主机不可为空")
private String dbHost;
@NotBlank(message = "数据库名称不可为空")
private String dbName;
@NotBlank(message = "数据库用户名不可为空")
private String dbUsername;
@NotBlank(message = "数据库密码不可为空")
private String dbPassword;
/**
* user 相关
*/
@NotBlank(message = "用户邮箱地址不可为空")
private String email;
@NotBlank(message = "用户密码不可为空")
private String password;
}

View File

@@ -32,8 +32,8 @@ public class Response<T> implements Serializable {
* @param result 结果
* @return Response
*/
public static Response success(Object result) {
return new Response(ResponseEnum.SUCCESS.getCode(), ResponseEnum.SUCCESS.getMsg(), result);
public static <T> Response<T> success(T result) {
return new Response<T>(ResponseEnum.SUCCESS.getCode(), ResponseEnum.SUCCESS.getMsg(), result);
}
/**
@@ -42,8 +42,8 @@ public class Response<T> implements Serializable {
* @param result 结果
* @return Response
*/
public static Response failure(String result) {
return new Response(ResponseEnum.FAILURE.getCode(), ResponseEnum.FAILURE.getMsg(), result);
public static Response<String> failure(String result) {
return new Response<String>(ResponseEnum.FAILURE.getCode(), ResponseEnum.FAILURE.getMsg(), result);
}
/**
@@ -53,8 +53,8 @@ public class Response<T> implements Serializable {
* @param result 结果
* @return Response
*/
public static Response response(ResponseEnum r, String result) {
return new Response(r.getCode(), r.getMsg(), result);
public static <T> Response<T> response(ResponseEnum r, T result) {
return new Response<T>(r.getCode(), r.getMsg(), result);
}
@SneakyThrows

View File

@@ -0,0 +1,25 @@
package cn.celess.blog.entity.model;
/**
* @author : xiaohai
* @date : 2020/10/15 22:16
* @desc :
*/
public class FileInfo {
/**
* 文件名
*/
public String key;
/**
* 文件hash值
*/
public String hash;
/**
* 文件大小,单位:字节
*/
public long size;
/**
* 文件上传时间单位为100纳秒
*/
public long uploadTime;
}

View File

@@ -5,9 +5,9 @@ package cn.celess.blog.entity.model;
* @author : xiaohai
* @date : 2019/04/21 22:43
*/
public class QiniuResponse {
public class FileResponse {
public String key;
public String hash;
public String bucket;
public long fsize;
public String type;
public long size;
}

View File

@@ -10,7 +10,9 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.mail.SimpleMailMessage;
import org.springframework.validation.BindException;
import org.springframework.validation.BindingResult;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
@@ -18,6 +20,10 @@ import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
import javax.servlet.http.HttpServletRequest;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import javax.validation.ValidationException;
import java.util.Set;
/**
* @author : xiaohai
@@ -41,27 +47,32 @@ public class ExceptionHandle {
if (e instanceof MyException) {
MyException exception = (MyException) e;
logger.debug("返回了自定义的exception,[code={},msg={},result={}]", exception.getCode(), e.getMessage(), exception.getResult());
return new Response(exception.getCode(), e.getMessage(), exception.getResult());
return new Response<>(exception.getCode(), e.getMessage(), exception.getResult());
}
//请求路径不支持该方法
if (e instanceof HttpRequestMethodNotSupportedException) {
logger.debug("遇到请求路径与请求方法不匹配的请求,[msg={}path:{},method:{}]", e.getMessage(), request.getRequestURL(), request.getMethod());
return new Response(ResponseEnum.ERROR.getCode(), e.getMessage(), null);
return new Response<>(ResponseEnum.ERROR.getCode(), e.getMessage(), null);
}
//数据输入类型不匹配
if (e instanceof MethodArgumentTypeMismatchException) {
logger.debug("输入类型不匹配,[msg={}]", e.getMessage());
return new Response(ResponseEnum.PARAMETERS_ERROR.getCode(), "数据输入有问题,请修改后再访问", null);
return new Response<>(ResponseEnum.PARAMETERS_ERROR.getCode(), "数据输入有问题,请修改后再访问", null);
}
//数据验证失败
if (e instanceof BindException) {
logger.debug("数据验证失败,[msg={}]", e.getMessage());
return new Response(ResponseEnum.PARAMETERS_ERROR.getCode(), "数据输入有问题,请修改", null);
return new Response<>(ResponseEnum.PARAMETERS_ERROR.getCode(), "数据输入有问题,请修改", null);
}
//数据输入不完整
if (e instanceof MissingServletRequestParameterException) {
logger.debug("数据输入不完整,[msg={}]", e.getMessage());
return new Response(ResponseEnum.PARAMETERS_ERROR.getCode(), "数据输入不完整,请检查", null);
return new Response<>(ResponseEnum.PARAMETERS_ERROR.getCode(), "数据输入不完整,请检查", null);
}
if (e instanceof MethodArgumentNotValidException) {
logger.debug("数据验证失败,[msg: {}]", e.getMessage());
BindingResult violations = ((MethodArgumentNotValidException) e).getBindingResult();
return new Response<>(ResponseEnum.PARAMETERS_ERROR.getCode(), violations.getAllErrors().get(0).getDefaultMessage(), null);
}
// 发送错误信息到邮箱
@@ -70,7 +81,7 @@ public class ExceptionHandle {
sendMessage(e);
}
e.printStackTrace();
return new Response(ResponseEnum.ERROR.getCode(), "服务器出现错误,已记录", null);
return new Response<>(ResponseEnum.ERROR.getCode(), "服务器出现错误,已记录", null);
}
/**

View File

@@ -0,0 +1,55 @@
package cn.celess.blog.mapper;
import cn.celess.blog.entity.Config;
import org.apache.ibatis.annotations.Mapper;
import org.springframework.stereotype.Repository;
import java.util.List;
/**
* @author : xiaohai
* @date : 2020/10/16 15:24
* @desc :
*/
@Mapper
@Repository
public interface ConfigMapper {
/**
* 获取单个配置
*
* @param key 配置名
* @return 配置
*/
Config getConfiguration(String key);
/**
* 更新配置
*
* @param c 配置
* @return 改变数据行数
*/
int updateConfiguration(Config c);
/**
* 获取所有配置
*
* @return 所有配置
*/
List<Config> getConfigurations();
/**
* 新增配置
*
* @param c 配置
* @return 改变行数
*/
int addConfiguration(Config c);
/**
* 删除配置
*
* @param id 主键id
* @return 改变行数
*/
int deleteConfiguration(int id);
}

View File

@@ -0,0 +1,40 @@
package cn.celess.blog.service;
import cn.celess.blog.entity.model.FileInfo;
import cn.celess.blog.entity.model.FileResponse;
import org.springframework.stereotype.Service;
import java.io.InputStream;
import java.util.List;
/**
* @author : xiaohai
* @date : 2020/10/15 18:19
* @desc : 文件管理器 定义操作文件的方法
*/
@Service
public interface FileManager {
/**
* 上传文件到文件存储容器中
*
* @param is 文件流
* @param fileName 文件名
* @return FileResponse
*/
FileResponse uploadFile(InputStream is, String fileName);
/**
* 获取文件列表
*
* @return 文件信息
*/
List<FileInfo> getFileList();
/**
* 删除文件
*
* @param fileName 文件名
* @return 是否删除成功
*/
boolean deleteFile(String fileName);
}

View File

@@ -0,0 +1,18 @@
package cn.celess.blog.service;
import org.springframework.stereotype.Service;
/**
* @author : xiaohai
* @date : 2019/04/25 18:15
*/
@Service("fileService")
@FunctionalInterface
public interface FileService {
/**
* 获取文件管理器
*
* @return 文件管理器 可以操作文件
*/
FileManager getFileManager();
}

View File

@@ -0,0 +1,20 @@
package cn.celess.blog.service;
import cn.celess.blog.entity.InstallParam;
import org.springframework.stereotype.Service;
import javax.validation.constraints.NotNull;
import java.util.Map;
/**
* @author : xiaohai
* @date : 2020/10/23 16:22
* @desc :
*/
@Service
public interface InstallService {
Map<String, Object> isInstall();
Map<String, Object> install(@NotNull InstallParam installParam);
}

View File

@@ -1,31 +0,0 @@
package cn.celess.blog.service;
import cn.celess.blog.entity.model.QiniuResponse;
import com.qiniu.storage.model.FileInfo;
import org.springframework.stereotype.Service;
import java.io.InputStream;
/**
* @author : xiaohai
* @date : 2019/04/25 18:15
*/
@Service
public interface QiniuService {
/**
* 上传文件
*
* @param is InputStream流
* @param fileName 文件名
* @return 响应数据
*/
QiniuResponse uploadFile(InputStream is, String fileName);
/**
* 获取文件列表
*
* @return 文件列表
*/
FileInfo[] getFileList();
}

View File

@@ -0,0 +1,36 @@
package cn.celess.blog.service.fileserviceimpl;
import cn.celess.blog.enmu.ConfigKeyEnum;
import cn.celess.blog.service.FileManager;
import cn.celess.blog.service.FileService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
/**
* @author : xiaohai
* @date : 2020/10/15 18:52
* @desc : 提供文件管理器的服务
*/
@Slf4j
@Service("fileServiceImpl")
public class FileServiceImpl implements FileService {
@Resource(name = "qiniuFileServiceImpl")
FileManager qiniuFileManager;
@Resource(name = "localFileServiceImpl")
FileManager localFileServiceImpl;
@Override
public FileManager getFileManager() {
String property = System.getProperty(ConfigKeyEnum.FILE_TYPE.getKey());
log.info("获取到{}:{}", ConfigKeyEnum.FILE_TYPE.getKey(), property);
if ("qiniu".equals(property)){
return qiniuFileManager;
}else if ("local".equals(property)){
return localFileServiceImpl;
}
return localFileServiceImpl;
}
}

View File

@@ -0,0 +1,104 @@
package cn.celess.blog.service.fileserviceimpl;
import cn.celess.blog.enmu.ConfigKeyEnum;
import cn.celess.blog.entity.model.FileInfo;
import cn.celess.blog.entity.model.FileResponse;
import cn.celess.blog.service.FileManager;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.net.URLEncoder;
import java.nio.file.Files;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.ArrayList;
import java.util.List;
/**
* @author : xiaohai
* @date : 2020/10/16 14:39
* @desc :
*/
@Slf4j
@Service("localFileServiceImpl")
public class LocalFileServiceImpl implements FileManager {
private static String path = null;
@SneakyThrows
@Override
public FileResponse uploadFile(InputStream is, String fileName) {
if (path == null) {
path = System.getProperty(ConfigKeyEnum.FILE_LOCAL_DIRECTORY_PATH.getKey());
}
// 判断文件夹是否存在
File directory = new File(getPath(path));
if (!directory.exists() && directory.mkdirs()) {
log.info("不存在文件夹,创建文件夹=>{}", directory.getAbsolutePath());
}
log.info("上传文件 {}", fileName);
// 存储文件
File file1 = new File(getPath(path) + File.separator + fileName);
FileOutputStream fos = new FileOutputStream(file1);
byte[] cache = new byte[1024 * 100];
int len = 0;
FileResponse fileResponse = new FileResponse();
while ((len = is.read(cache)) != -1) {
fileResponse.size += len;
fos.write(cache, 0, len);
}
fos.flush();
fos.close();
is.close();
fileResponse.key = URLEncoder.encode(fileName, "UTF-8");
fileResponse.type = "local";
return fileResponse;
}
@SneakyThrows
@Override
public List<FileInfo> getFileList() {
if (path == null) {
path = System.getProperty(ConfigKeyEnum.FILE_LOCAL_DIRECTORY_PATH.getKey());
}
File file = new File(getPath(path));
File[] files = file.listFiles();
List<FileInfo> fileInfoList = new ArrayList<>();
if (files == null) {
return null;
}
for (File file1 : files) {
FileInfo fileInfo = new FileInfo();
fileInfo.key = URLEncoder.encode(file1.getName(), "UTF-8");
fileInfo.size = file1.length();
BasicFileAttributes basicFileAttributes = Files.readAttributes(file1.toPath(), BasicFileAttributes.class);
fileInfo.uploadTime = basicFileAttributes.creationTime().toMillis();
fileInfoList.add(fileInfo);
}
return fileInfoList;
}
@Override
public boolean deleteFile(String fileName) {
if (path == null) {
path = System.getProperty(ConfigKeyEnum.FILE_LOCAL_DIRECTORY_PATH.getKey());
}
File file = new File(getPath(path) + File.separator + fileName);
return file.delete();
}
public static String getPath(String path) {
if (path == null) {
return "";
}
String pathCop = path.replaceAll("//", File.separator);
if (path.startsWith("~")) {
// 家目录
pathCop = path.replaceFirst("~", "");
pathCop = System.getProperty("user.home") + pathCop;
}
return pathCop;
}
}

View File

@@ -0,0 +1,120 @@
package cn.celess.blog.service.fileserviceimpl;
import cn.celess.blog.enmu.ConfigKeyEnum;
import cn.celess.blog.entity.model.FileInfo;
import cn.celess.blog.entity.model.FileResponse;
import cn.celess.blog.service.FileManager;
import com.qiniu.common.QiniuException;
import com.qiniu.common.Zone;
import com.qiniu.http.Response;
import com.qiniu.storage.BucketManager;
import com.qiniu.storage.Configuration;
import com.qiniu.storage.UploadManager;
import com.qiniu.util.Auth;
import com.qiniu.util.StringMap;
import lombok.SneakyThrows;
import org.springframework.stereotype.Service;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
/**
* @author : xiaohai
* @date : 2019/04/25 18:15
*/
@Service("qiniuFileServiceImpl")
public class QiniuFileServiceImpl implements FileManager {
private static final Configuration cfg = new Configuration(Zone.zone2());
private static UploadManager uploadManager;
private static BucketManager bucketManager;
private static Auth auth;
private String bucket;
/**
* todo :: 添加reload 方法 配置修改重新创建对象
*/
private void init() {
String accessKey = System.getProperty(ConfigKeyEnum.FILE_QINIU_ACCESS_KEY.getKey());
String secretKey = System.getProperty(ConfigKeyEnum.FILE_QINIU_SECRET_KEY.getKey());
this.bucket = System.getProperty(ConfigKeyEnum.FILE_QINIU_BUCKET.getKey());
if (auth == null || uploadManager == null || bucketManager == null) {
auth = Auth.create(accessKey, secretKey);
uploadManager = new UploadManager(cfg);
bucketManager = new BucketManager(auth, cfg);
}
}
@Override
public FileResponse uploadFile(InputStream is, String fileName) {
init();
//文件存在则删除文件
if (continueFile(fileName)) {
try {
System.out.println(bucketManager.delete(bucket, fileName).toString());
} catch (QiniuException e) {
e.printStackTrace();
}
}
//上传
try {
Response response = uploadManager.put(is, fileName, auth.uploadToken(bucket), null, null);
FileResponse fileResponse = new FileResponse();
StringMap stringMap = response.jsonToMap();
fileResponse.key = (String) stringMap.get("key");
fileResponse.type = "qiniu";
fileResponse.size = 0;
fileResponse.hash = (String) stringMap.get("hash");
return fileResponse;
} catch (QiniuException e) {
Response r = e.response;
System.err.println(r.toString());
return null;
}
}
@Override
public List<FileInfo> getFileList() {
init();
List<FileInfo> infoList = null;
BucketManager.FileListIterator fileListIterator = bucketManager.createFileListIterator(bucket, "", 1000, "");
while (fileListIterator.hasNext()) {
//处理获取的file list结果
infoList = new ArrayList<com.qiniu.storage.model.FileInfo>(Arrays.asList(fileListIterator.next()))
.stream()
.map(item -> {
FileInfo fileInfo = new FileInfo();
fileInfo.key = item.key;
fileInfo.hash = item.hash;
fileInfo.size = item.fsize;
fileInfo.uploadTime = item.putTime;
return fileInfo;
})
.collect(Collectors.toList());
}
return infoList;
}
@SneakyThrows
@Override
public boolean deleteFile(String fileName) {
init();
Response response = bucketManager.delete(bucket, fileName);
return "".equals(response.bodyString());
}
private boolean continueFile(String key) {
List<FileInfo> fileList = getFileList();
for (FileInfo fileInfo : fileList) {
if (key.equals(fileInfo.key)) {
return true;
}
}
return false;
}
}

View File

@@ -0,0 +1,118 @@
package cn.celess.blog.service.serviceimpl;
import cn.celess.blog.BlogApplication;
import cn.celess.blog.enmu.ConfigKeyEnum;
import cn.celess.blog.enmu.ResponseEnum;
import cn.celess.blog.entity.Config;
import cn.celess.blog.entity.InstallParam;
import cn.celess.blog.entity.User;
import cn.celess.blog.exception.MyException;
import cn.celess.blog.mapper.ConfigMapper;
import cn.celess.blog.mapper.UserMapper;
import cn.celess.blog.service.InstallService;
import cn.celess.blog.service.fileserviceimpl.LocalFileServiceImpl;
import cn.celess.blog.util.MD5Util;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.validation.constraints.NotNull;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import java.util.stream.Collectors;
/**
* @author : xiaohai
* @date : 2020/10/23 16:23
* @desc :
*/
@Slf4j
@Service
public class InstallServiceImpl implements InstallService {
public static final String MYSQL_DRIVER_CLASS_NAME = "com.mysql.cj.jdbc.Driver";
public static final String H2_DRIVER_CLASS_NAME = "org.h2.Driver";
@Autowired
ConfigMapper configMapper;
@Autowired
UserMapper userMapper;
@Override
public Map<String, Object> isInstall() {
Config installed = configMapper.getConfiguration(ConfigKeyEnum.BLOG_INSTALLED.getKey());
Boolean isInstall = Boolean.valueOf(installed.getValue());
Map<String, Object> result = new HashMap<>(3);
result.put("is_install", isInstall);
if (isInstall) {
Map<String, String> configMap = configMapper.getConfigurations()
.stream()
.filter(config -> config.getValue() != null)
.collect(Collectors.toMap(Config::getName, Config::getValue));
result.put(ConfigKeyEnum.FILE_TYPE.getKey(), configMap.get(ConfigKeyEnum.FILE_TYPE.getKey()));
result.put(ConfigKeyEnum.DB_TYPE.getKey(), configMap.get(ConfigKeyEnum.DB_TYPE.getKey()));
}
return result;
}
@SneakyThrows
@Override
@Transactional(rollbackFor = Exception.class)
public Map<String, Object> install(@NotNull InstallParam installParam) {
User user = new User(installParam.getEmail(), MD5Util.getMD5(installParam.getPassword()));
userMapper.addUser(user);
userMapper.setUserRole(user.getId(), "admin");
Config install = configMapper.getConfiguration(ConfigKeyEnum.BLOG_INSTALLED.getKey());
boolean isInstall = Boolean.parseBoolean(install.getValue());
if (isInstall) {
throw new MyException(ResponseEnum.FAILURE, "已经安装过了");
}
Config config = new Config(ConfigKeyEnum.DB_TYPE);
config.setValue(installParam.getDbType());
Properties properties = new Properties();
StringBuilder urlSb = new StringBuilder();
if ("h2".equals(installParam.getDbType())) {
properties.setProperty(ConfigKeyEnum.DB_DRIVER_CLASS_NAME.getKey(), H2_DRIVER_CLASS_NAME);
urlSb.append("jdbc:h2:");
} else {
properties.setProperty(ConfigKeyEnum.DB_DRIVER_CLASS_NAME.getKey(), MYSQL_DRIVER_CLASS_NAME);
urlSb.append("jdbc:mysql:");
}
// TODO ::
urlSb.append(installParam.getDbHost()).append('/').append(installParam.getDbName());
properties.setProperty(ConfigKeyEnum.DB_URL.getKey(), urlSb.toString());
properties.setProperty(ConfigKeyEnum.DB_USERNAME.getKey(), installParam.getDbUsername());
properties.setProperty(ConfigKeyEnum.DB_PASSWORD.getKey(), installParam.getDbPassword());
Config configuration = configMapper.getConfiguration(ConfigKeyEnum.BLOG_DB_PATH.getKey());
File file = new File(LocalFileServiceImpl.getPath(configuration.getValue()));
if (!file.exists() && file.createNewFile()) {
log.info("创建数据库配置文件: {}", file.getAbsolutePath());
}
FileInputStream fis = new FileInputStream(file);
FileOutputStream fos = new FileOutputStream(file);
properties.load(fis);
configMapper.addConfiguration(config);
properties.store(fos, "DB CONFIG");
install.setValue(Boolean.valueOf(true).toString());
configMapper.updateConfiguration(install);
log.info("重启...");
// 重启
BlogApplication.restart();
return null;
}
}

View File

@@ -1,87 +0,0 @@
package cn.celess.blog.service.serviceimpl;
import cn.celess.blog.entity.model.QiniuResponse;
import cn.celess.blog.service.QiniuService;
import com.qiniu.common.QiniuException;
import com.qiniu.common.Zone;
import com.qiniu.http.Response;
import com.qiniu.storage.BucketManager;
import com.qiniu.storage.Configuration;
import com.qiniu.storage.UploadManager;
import com.qiniu.storage.model.FileInfo;
import com.qiniu.util.Auth;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import java.io.InputStream;
/**
* @author : xiaohai
* @date : 2019/04/25 18:15
*/
@Service
public class QiniuServiceImpl implements QiniuService {
private static final Configuration cfg = new Configuration(Zone.zone2());
private static UploadManager uploadManager;
private static BucketManager bucketManager;
private static Auth auth;
@Value("${qiniu.accessKey}")
private String accessKey;
@Value("${qiniu.secretKey}")
private String secretKey;
@Value("${qiniu.bucket}")
private String bucket;
private void init() {
if (auth == null || uploadManager == null || bucketManager == null) {
auth = Auth.create(accessKey, secretKey);
uploadManager = new UploadManager(cfg);
bucketManager = new BucketManager(auth, cfg);
}
}
@Override
public QiniuResponse uploadFile(InputStream is, String fileName) {
init();
//文件存在则删除文件
if (continueFile(fileName)) {
try {
System.out.println(bucketManager.delete(bucket, fileName).toString());
} catch (QiniuException e) {
e.printStackTrace();
}
}
//上传
try {
Response response = uploadManager.put(is, fileName, auth.uploadToken(bucket), null, null);
return response.jsonToObject(QiniuResponse.class);
} catch (QiniuException e) {
Response r = e.response;
System.err.println(r.toString());
return null;
}
}
@Override
public FileInfo[] getFileList() {
init();
BucketManager.FileListIterator fileListIterator = bucketManager.createFileListIterator(bucket, "", 1000, "");
FileInfo[] items = null;
while (fileListIterator.hasNext()) {
//处理获取的file list结果
items = fileListIterator.next();
}
return items;
}
private boolean continueFile(String key) {
FileInfo[] allFile = getFileList();
for (FileInfo fileInfo : allFile) {
if (key.equals(fileInfo.key)) {
return true;
}
}
return false;
}
}

View File

@@ -5,15 +5,15 @@ import cn.celess.blog.enmu.RoleEnum;
import cn.celess.blog.enmu.UserAccountStatusEnum;
import cn.celess.blog.entity.Response;
import cn.celess.blog.entity.User;
import cn.celess.blog.entity.model.FileResponse;
import cn.celess.blog.entity.model.PageData;
import cn.celess.blog.entity.model.QiniuResponse;
import cn.celess.blog.entity.model.UserModel;
import cn.celess.blog.entity.request.LoginReq;
import cn.celess.blog.entity.request.UserReq;
import cn.celess.blog.exception.MyException;
import cn.celess.blog.mapper.UserMapper;
import cn.celess.blog.service.FileService;
import cn.celess.blog.service.MailService;
import cn.celess.blog.service.QiniuService;
import cn.celess.blog.service.UserService;
import cn.celess.blog.util.*;
import com.github.pagehelper.PageHelper;
@@ -24,6 +24,7 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.mail.SimpleMailMessage;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.beans.Transient;
import java.io.InputStream;
@@ -45,8 +46,8 @@ public class UserServiceImpl implements UserService {
HttpServletRequest request;
@Autowired
MailService mailService;
@Autowired
QiniuService qiniuService;
@Resource(name = "fileServiceImpl")
FileService fileService;
@Autowired
RedisUtil redisUtil;
@Autowired
@@ -185,7 +186,7 @@ public class UserServiceImpl implements UserService {
@Override
public Object updateUserAavatarImg(InputStream is, String mime) {
User user = redisUserUtil.get();
QiniuResponse upload = qiniuService.uploadFile(is, user.getEmail() + "_" + user.getId() + mime.toLowerCase());
FileResponse upload = fileService.getFileManager().uploadFile(is, user.getEmail() + "_" + user.getId() + mime.toLowerCase());
user.setAvatarImgUrl(upload.key);
userMapper.updateAvatarImgUrl(upload.key, user.getId());
redisUserUtil.set(user);

View File

@@ -0,0 +1,65 @@
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.url=jdbc:h2:~/blog/db
spring.datasource.driver-class-name=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=sa
spring.datasource.platform=h2
spring.datasource.sql-script-encoding=utf-8
spring.datasource.initialization-mode=never
spring.datasource.schema=classpath:sql/schema-h2.sql
spring.datasource.data=classpath:sql/data.sql
spring.h2.console.path=/h2-console
spring.h2.console.enabled=true
# 生成JWT时候的密钥
jwt.secret=11111222223333444455556667778888
# sitemap 存放地址
sitemap.path=/www/wwwroot/celess.cn/sitemap.xml
############### email ##############
spring.mail.host=smtp.163.com
spring.mail.username=
spring.mail.password=
spring.mail.properties.mail.smtp.auth=true
spring.mail.properties.mail.smtp.starttls.enable=true
spring.mail.properties.mail.smtp.starttls.required=true
spring.mail.default-encoding=UTF-8
spring.mail.port=465
spring.mail.properties.mail.smtp.socketFactory.port=465
spring.mail.properties.mail.smtp.socketFactory.class=javax.net.ssl.SSLSocketFactory
spring.mail.properties.mail.smtp.socketFactory.fallback=false
################## mybatis ##################
mybatis.mapper-locations=classpath:mapper/*.xml
mybatis.type-aliases-package=cn.celess.blog.entity
pagehelper.helper-dialect=mysql
pagehelper.reasonable=true
pagehelper.support-methods-arguments=true
pagehelper.params=count=countSql
############### redis ##############
# REDIS (RedisProperties)
# Redis数据库索引默认为0
spring.redis.database=1
# Redis服务器地址
spring.redis.host=127.0.0.1
# Redis服务器连接端口 解决端口冲突 防止使用本地的redis
spring.redis.port=6379
# Redis服务器连接密码默认为空
spring.redis.password=
# 连接池最大连接数(使用负值表示没有限制)
spring.redis.jedis.pool.max-active=-1
# 连接池最大阻塞等待时间(使用负值表示没有限制)
spring.redis.jedis.pool.max-wait=-1
# 连接池中的最大空闲连接
spring.redis.jedis.pool.max-idle=8
# 连接池中的最小空闲连接
spring.redis.jedis.pool.min-idle=0
# 连接超时时间(毫秒)
spring.redis.timeout=5000

View File

@@ -1,9 +1,9 @@
server.port=8081
# 七牛的密钥配置
qiniu.accessKey=
qiniu.secretKey=
qiniu.bucket=
file.qiniu.accessKey=
file.qiniu.secretKey=
file.qiniu.bucket=
# sitemap 存放地址
sitemap.path=
# 生成JWT时候的密钥
@@ -62,12 +62,6 @@ spring.mail.properties.mail.smtp.socketFactory.class=javax.net.ssl.SSLSocketFact
spring.mail.properties.mail.smtp.socketFactory.fallback=false
#### 用于nginx的代理 获取真实ip
server.use-forward-headers = true
server.tomcat.remote-ip-header = X-Real-IP
server.tomcat.protocol-header = X-Forwarded-Proto
############### redis ##############
# REDIS (RedisProperties)
# Redis数据库索引默认为0

View File

@@ -1,9 +1,9 @@
server.port=8081
# 七牛的密钥配置
qiniu.accessKey=
qiniu.secretKey=
qiniu.bucket=
file.qiniu.accessKey=${QINIU_ACCESSKEY:null}
file.qiniu.secretKey=${QINIU_SECRETKEY:null}
file.qiniu.bucket=xiaohai
# sitemap 存放地址
sitemap.path=
# 生成JWT时候的密钥
@@ -32,10 +32,9 @@ spring.datasource.username=sa
spring.datasource.password=
spring.datasource.platform=h2
spring.datasource.sql-script-encoding=utf-8
spring.datasource.initialization-mode=ALWAYS
spring.datasource.schema=classpath:sql/schema_h2.sql
spring.datasource.schema=classpath:sql/schema-h2.sql
spring.datasource.data=classpath:sql/data.sql
@@ -50,13 +49,6 @@ pagehelper.support-methods-arguments=true
pagehelper.params=count=countSql
#### 用于nginx的代理 获取真实ip
server.use-forward-headers = true
server.tomcat.remote-ip-header = X-Real-IP
server.tomcat.protocol-header = X-Forwarded-Proto
############### email ##############
spring.mail.host=smtp.163.com
spring.mail.username=

View File

@@ -1,7 +1,10 @@
spring.profiles.active=prod
####七牛的配置
####cn.celess.blog.service.serviceimpl.QiniuServiceImpl
spring.profiles.active=easyDeploy
logging.level.cn.celess.blog=debug
logging.level.cn.celess.blog.mapper=info
#### 用于nginx的代理 获取真实ip
server.use-forward-headers = true
server.tomcat.remote-ip-header = X-Real-IP
server.tomcat.protocol-header = X-Forwarded-Proto
## 修改openSource 添加-test 文件用于测试 -prod文件用于线上发布

View File

@@ -0,0 +1,50 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.celess.blog.mapper.ConfigMapper">
<resultMap type="cn.celess.blog.entity.Config" id="ConfigMap">
<result property="id" column="conf_id" jdbcType="INTEGER"/>
<result property="name" column="conf_name" jdbcType="VARCHAR"/>
<result property="value" column="conf_value" jdbcType="VARCHAR"/>
</resultMap>
<!--查询单个-->
<select id="getConfiguration" resultMap="ConfigMap">
select conf_id,
conf_name,
conf_value
from config
where conf_name = #{key}
</select>
<!--查询指定行数据-->
<select id="getConfigurations" resultMap="ConfigMap">
select conf_id,
conf_name,
conf_value
from config
</select>
<!--新增所有列-->
<insert id="addConfiguration" keyProperty="id" useGeneratedKeys="true">
insert into config(conf_name, conf_value)
values (#{name}, #{value})
</insert>
<!--通过主键修改数据-->
<update id="updateConfiguration">
update config
set conf_value = #{value}
where conf_id = #{id}
</update>
<!--通过主键删除-->
<delete id="deleteConfiguration">
delete
from config
where conf_id = #{id}
</delete>
</mapper>

View File

@@ -0,0 +1,110 @@
* {
margin: 0;
padding: 0;
}
html, body {
width: 100%;
height: 100%;
}
.box {
width: 600px;
height: 45vh;
padding: 8px 10px;
border: 1px solid rgba(50, 50, 50, .1);
border-radius: 3px;
position: relative;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
.box-inner {
height: 100%;
width: 100%;
overflow: hidden;
}
.box-inner select, input, button {
width: 90%;
margin: 12px 5%;
height: 40px;
padding: 5px 10px;
border-radius: 3px;
border: 1px solid rgba(50, 50, 50, .1);
box-sizing: border-box;
}
input:focus {
border: 1px solid rgba(0, 50, 50, .6);
outline: none;
}
.box-inner h3 {
text-align: center;
margin-bottom: 20px;
}
.err-msg {
display: none;
color: red;
width: 100%;
padding-left: 30px;
font-size: small;
font-weight: lighter;
}
.show-err-tip {
display: block;
}
#first-page, #second-page {
height: 100%;
margin: 10px 0;
}
.hidden {
height: 0 !important;
visibility: hidden;
position: absolute;
}
.show {
width: 100%;
position: relative;
height: 100%;
visibility: visible;
}
#next-step, .button-group {
position: absolute;
left: 0;
right: 0;
bottom: 10px;
}
.button-group button {
width: 80px;
}
.button-group #install {
position: absolute;
bottom: 0;
right: 20px;
background: #1890ff;
color: white;
}
.loading-show {
z-index: 10000;
display: flex !important;
position: fixed;
left: 0;
right: 0;
bottom: 0;
top: 0;
}

View File

@@ -0,0 +1,171 @@
.pacman {
position: relative;
}
.loading {
box-sizing: border-box;
flex: 0 1 auto;
display: none;
flex-direction: column;
flex-grow: 1;
flex-shrink: 0;
flex-basis: 100%;
max-width: 100%;
/*height: 200px;*/
align-items: center;
justify-content: center;
background: #ececec;
}
.pacman > div:nth-child(2) {
-webkit-animation: pacman-balls 1s 0s infinite linear;
animation: pacman-balls 1s 0s infinite linear;
}
.pacman > div:nth-child(3) {
-webkit-animation: pacman-balls 1s 0.33s infinite linear;
animation: pacman-balls 1s 0.33s infinite linear;
}
.pacman > div:nth-child(4) {
-webkit-animation: pacman-balls 1s 0.66s infinite linear;
animation: pacman-balls 1s 0.66s infinite linear;
}
.pacman > div:nth-child(5) {
-webkit-animation: pacman-balls 1s 0.99s infinite linear;
animation: pacman-balls 1s 0.99s infinite linear;
}
.pacman > div:first-of-type {
width: 0px;
height: 0px;
border-right: 25px solid transparent;
border-top: 25px solid #ed5565;
border-left: 25px solid #ed5565;
border-bottom: 25px solid #ed5565;
border-radius: 25px;
-webkit-animation: rotate_pacman_half_up 0.5s 0s infinite;
animation: rotate_pacman_half_up 0.5s 0s infinite;
}
.pacman > div:nth-child(2) {
width: 0px;
height: 0px;
border-right: 25px solid transparent;
border-top: 25px solid #ed5565;
border-left: 25px solid #ed5565;
border-bottom: 25px solid #ed5565;
border-radius: 25px;
-webkit-animation: rotate_pacman_half_down 0.5s 0s infinite;
animation: rotate_pacman_half_down 0.5s 0s infinite;
margin-top: -50px;
}
.pacman > div:nth-child(3), .pacman > div:nth-child(4), .pacman > div:nth-child(5), .pacman > div:nth-child(6) {
background-color: #ed5565;
width: 15px;
height: 15px;
border-radius: 100%;
margin: 2px;
width: 10px;
height: 10px;
position: absolute;
-webkit-transform: translate(0, -6.25px);
-ms-transform: translate(0, -6.25px);
transform: translate(0, -6.25px);
top: 25px;
left: 100px;
}
@-webkit-keyframes rotate_pacman_half_up {
0% {
-webkit-transform: rotate(270deg);
transform: rotate(270deg);
}
50% {
-webkit-transform: rotate(360deg);
transform: rotate(360deg);
}
100% {
-webkit-transform: rotate(270deg);
transform: rotate(270deg);
}
}
@keyframes rotate_pacman_half_up {
0% {
-webkit-transform: rotate(270deg);
transform: rotate(270deg);
}
50% {
-webkit-transform: rotate(360deg);
transform: rotate(360deg);
}
100% {
-webkit-transform: rotate(270deg);
transform: rotate(270deg);
}
}
@-webkit-keyframes rotate_pacman_half_down {
0% {
-webkit-transform: rotate(90deg);
transform: rotate(90deg);
}
50% {
-webkit-transform: rotate(0deg);
transform: rotate(0deg);
}
100% {
-webkit-transform: rotate(90deg);
transform: rotate(90deg);
}
}
@keyframes rotate_pacman_half_down {
0% {
-webkit-transform: rotate(90deg);
transform: rotate(90deg);
}
50% {
-webkit-transform: rotate(0deg);
transform: rotate(0deg);
}
100% {
-webkit-transform: rotate(90deg);
transform: rotate(90deg);
}
}
@-webkit-keyframes pacman-balls {
75% {
opacity: 0.7;
}
100% {
-webkit-transform: translate(-100px, -6.25px);
transform: translate(-100px, -6.25px);
}
}
@keyframes pacman-balls {
75% {
opacity: 0.7;
}
100% {
-webkit-transform: translate(-100px, -6.25px);
transform: translate(-100px, -6.25px);
}
}

View File

@@ -0,0 +1,164 @@
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<title>博客安装页面</title>
<link rel="stylesheet" href="css/install.css">
<link rel="stylesheet" href="css/loading.css">
<script src="//cdn.bootcdn.net/ajax/libs/jquery/1.10.0/jquery.min.js"></script>
</head>
<body>
<div class="loading">
<div class="loader-inner pacman">
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
</div>
<p style="margin-top: 20px;margin-left: 10px;">
加载中....
</p>
</div>
<div class="box">
<div class="box-inner">
<div id="first-page" class="show">
<h3>数据库配置</h3>
<!-- 数据库-->
<select id="db-type">
<option value="h2">H2数据库</option>
<option value="mysql">Mysql数据库</option>
</select>
<input id="db-host" type="text" placeholder="请输入数据库的主机地址">
<span class="err-msg" id="err-tip-db-host">数据库的主机地址不可为空</span>
<input id="db-name" type="text" placeholder="请输入数据库的名称">
<span class="err-msg" id="err-tip-db-name">数据库的地址名称不可为空</span>
<input id="db-username" type="text" placeholder="请输入数据库用户名">
<span class="err-msg" id="err-tip-db-username">数据库用户名不可为空</span>
<input id="db-password" type="password" placeholder="请输入数据库密码">
<span class="err-msg" id="err-tip-db-password">数据库密码不可为空</span>
<button onclick="nextPage()" id="next-step">下一步</button>
</div>
<div id="second-page" class="hidden">
<h3>新建管理员用户</h3>
<input id="email" type="email" placeholder="请输入用户名">
<span class="err-msg" id="err-tip-email">用户名不可为空</span>
<input id="password" type="password" placeholder="请输入密码">
<span class="err-msg" id="err-tip-password">密码不可为空</span>
<div class="button-group">
<button onclick="preStep()" id="pre-step">上一步</button>
<button onclick="postInstallEvent()" id="install"> 安装</button>
</div>
</div>
</div>
</div>
<script type="text/javascript">
let requestData;
let interValId;
function postInstallEvent() {
requestData.email = $('#email').val()
requestData.password = $('#password').val()
$('#err-tip-email').removeClass("show-err-tip");
$('#err-tip-password').removeClass("show-err-tip");
!requestData.password && checkInput($('#password'), $('#err-tip-password'))
!requestData.email && checkInput($('#email'), $('#err-tip-email'))
if (!requestData.email || !requestData.password) return;
$('.loading').addClass('loading-show');
$.ajax('/install', {
method: "POST",
contentType: 'application/json',
data: JSON.stringify(requestData),
withCredentials: true,
dataType: 'json',
success: function (data) {
console.log("data", data);
console.log("requestData", requestData);
interValId = setInterval(checkConnection, 500)
checkConnection();
}
})
}
function checkConnection() {
$.ajax('/is_install', {
method: "GET",
dataType: 'json',
success: function (data) {
if (data.result.is_install) {
console.log("安装成功");
window.location.href = '/';
} else {
console.log("安装失败");
}
$('.loading').removeClass('loading-show');
clearInterval(interValId);
},
error: function (err) {
console.log(err);
}
})
}
/**
* 切换下一页
*/
function nextPage() {
requestData = {
dbPassword: $('#db-password').val(),
dbType: $('#db-type').val(),
dbHost: $('#db-host').val(),
dbName: $('#db-name').val(),
dbUsername: $('#db-username').val(),
email: '',
password: '',
}
$('#err-tip-db-type').removeClass("show-err-tip");
$('#err-tip-db-host').removeClass("show-err-tip");
$('#err-tip-db-name').removeClass("show-err-tip");
$('#err-tip-db-username').removeClass("show-err-tip");
$('#err-tip-db-password').removeClass("show-err-tip");
!requestData.dbType && checkInput($('#db-type'), $('#err-tip-db-type'))
!requestData.dbHost && checkInput($('#db-host'), $('#err-tip-db-host'))
!requestData.dbName && checkInput($('#db-name'), $('#err-tip-db-name'))
!requestData.dbUsername && checkInput($('#db-username'), $('#err-tip-db-username'))
!requestData.dbPassword && checkInput($('#db-password'), $('#err-tip-db-password'))
if (!requestData.dbType || !requestData.dbUrl || !requestData.dbUsername || !requestData.dbPassword) return
$('#first-page').addClass('hidden');
$('#first-page').removeClass('show');
$('#second-page').addClass('show');
$('#second-page').removeClass('hidden');
}
function preStep() {
$('#second-page').addClass('hidden');
$('#second-page').removeClass('show');
$('#first-page').addClass('show');
$('#first-page').removeClass('hidden');
$('#err-tip-email').removeClass("show-err-tip");
$('#err-tip-password').removeClass("show-err-tip");
}
/**
* 检查组件的状态
* @param component 输入框
* @param errTipComponent 错误信息提示框
* @returns {boolean} 是否有错误
*/
function checkInput(component, errTipComponent) {
if (!component.val()) {
errTipComponent.addClass("show-err-tip");
component.focus();
return true;
}
return false;
}
</script>
</body>
</html>

View File

@@ -214,3 +214,12 @@ VALUES (1, '1.新增网站更新接口api \n2.新增友链api \n3.优化了文
(16, '登陆处理过程变更登陆时长修改至5天', '2019-11-22 11:39:03', 0),
(17, '界面改版v2.0', '2020-04-06 11:00:53', 0);
INSERT INTO config (conf_id, conf_name, conf_value)
VALUES (1, 'file.type', 'local'),
(2, 'file.qiniu.accessKey', null),
(3, 'file.qiniu.secretKey', null),
(4, 'file.qiniu.bucket', null),
(5, 'blog.file.path', '~/blog/'),
(6, 'file.local.directoryPath', '~/blog/files/'),
(8, 'blog.installed', 'false'),
(9, 'blog.db.path', '~/blog/db.properties')

View File

@@ -8,6 +8,7 @@ drop table if exists tag_category;
drop table if exists links;
drop table if exists visitor;
drop table if exists web_update;
drop table if exists config;
-- 用户表
CREATE TABLE `user`
@@ -115,6 +116,13 @@ CREATE TABLE `web_update`
`is_delete` boolean not null default false comment '该数据是否被删除'
);
CREATE TABLE `config`
(
`conf_id` int primary key auto_increment,
`conf_name` varchar(255) unique not null comment '配置名',
`conf_value` varchar(255) default null comment '配置值'
);
CREATE VIEW articleView
(articleId, title, summary, mdContent, url, isOriginal, readingCount, likeCount, dislikeCount,
publishDate, updateDate, isOpen,

View File

@@ -8,6 +8,7 @@ drop table if exists tag_category;
drop table if exists links;
drop table if exists visitor;
drop table if exists web_update;
drop table if exists config;
CREATE TABLE `user`
@@ -110,6 +111,13 @@ CREATE TABLE `web_update`
`is_delete` boolean not null default false comment '该数据是否被删除'
) comment '更新内容表';
CREATE TABLE `config`
(
`conf_id` int primary key auto_increment,
`conf_name` varchar(255) unique not null comment '配置名',
`conf_value` varchar(255) default null comment '配置值'
);
CREATE VIEW articleView
(articleId, title, summary, mdContent, url, isOriginal, readingCount, likeCount, dislikeCount,
publishDate, updateDate, isOpen,

View File

@@ -2,15 +2,15 @@ package cn.celess.blog;
import cn.celess.blog.entity.Response;
import cn.celess.blog.entity.model.QiniuResponse;
import cn.celess.blog.entity.model.FileInfo;
import cn.celess.blog.entity.model.FileResponse;
import cn.celess.blog.entity.model.UserModel;
import cn.celess.blog.entity.request.LoginReq;
import cn.celess.blog.service.MailService;
import cn.celess.blog.service.QiniuService;
import cn.celess.blog.service.FileManager;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.qiniu.storage.model.FileInfo;
import lombok.extern.slf4j.Slf4j;
import org.junit.After;
import org.junit.Before;
@@ -37,6 +37,8 @@ import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.UUID;
@@ -325,23 +327,29 @@ public class BaseTest {
}
@Slf4j
public static class TestQiNiuServiceImpl implements QiniuService {
public static class TestFileManager implements FileManager {
@Override
public QiniuResponse uploadFile(InputStream is, String fileName) {
QiniuResponse response = new QiniuResponse();
public FileResponse uploadFile(InputStream is, String fileName) {
FileResponse response = new FileResponse();
log.debug("上传文件请求,[fileName:{}]", fileName);
response.key = "key";
response.bucket = "bucket";
response.type = "test";
response.hash = "hash";
response.fsize = 1;
response.size = 1;
return response;
}
@Override
public FileInfo[] getFileList() {
public List<FileInfo> getFileList() {
log.debug("获取文件列表请求");
return new FileInfo[0];
return new ArrayList<>();
}
@Override
public boolean deleteFile(String fileName) {
log.debug("删除[{}]成功", fileName);
return true;
}
}
}

View File

@@ -0,0 +1,131 @@
package cn.celess.blog.configuration;
import cn.celess.blog.BaseTest;
import cn.celess.blog.enmu.ConfigKeyEnum;
import cn.celess.blog.service.fileserviceimpl.LocalFileServiceImpl;
import com.alibaba.druid.pool.DruidDataSource;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mock;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.env.Environment;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Arrays;
import java.util.Properties;
import static org.junit.Assert.*;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
public class DruidConfigTest extends BaseTest {
@Mock
Environment env;
@Autowired
Environment globalEnvironment;
private File configFile;
private File bakConfigFile;
@Test
public void initDataSource() throws IOException {
DruidConfig druidConfig = new DruidConfig();
druidConfig.env = env;
System.out.println(Arrays.toString(env.getActiveProfiles()));
// 无配置文件
assertTrue(!configFile.exists() || configFile.delete());
DruidDataSource druidDataSource = druidConfig.initDataSource();
// 加载初始化时候配置文件的数据库连接
assertEquals(env.getProperty(DruidConfig.DB_CONFIG_URL_PREFIX), druidDataSource.getUrl());
assertEquals(env.getProperty(DruidConfig.DB_CONFIG_DRIVER_CLASS_NAME_PREFIX), druidDataSource.getDriverClassName());
assertEquals(env.getProperty(DruidConfig.DB_CONFIG_USERNAME_PREFIX), druidDataSource.getUsername());
assertEquals(env.getProperty(DruidConfig.DB_CONFIG_PASSWORD_PREFIX), druidDataSource.getPassword());
assertTrue(configFile.createNewFile());
// 有配置文件的测试
Properties properties = new Properties();
FileInputStream fileInputStream = new FileInputStream(configFile);
properties.load(fileInputStream);
fileInputStream.close();
properties.setProperty(DruidConfig.DB_CONFIG_URL_PREFIX, "jdbc:mysql://localhost:3306/blog");
properties.setProperty(DruidConfig.DB_CONFIG_DRIVER_CLASS_NAME_PREFIX, "com.mysql.cj.jdbc.Driver");
properties.setProperty(DruidConfig.DB_CONFIG_USERNAME_PREFIX, "username");
properties.setProperty(DruidConfig.DB_CONFIG_PASSWORD_PREFIX, "password");
// 保存到文件
FileOutputStream fileOutputStream = new FileOutputStream(configFile);
properties.store(fileOutputStream, "数据库配置");
fileOutputStream.close();
druidDataSource = druidConfig.initDataSource();
assertEquals(properties.getProperty(DruidConfig.DB_CONFIG_URL_PREFIX), druidDataSource.getUrl());
assertEquals(properties.getProperty(DruidConfig.DB_CONFIG_DRIVER_CLASS_NAME_PREFIX), druidDataSource.getDriverClassName());
assertEquals(properties.getProperty(DruidConfig.DB_CONFIG_USERNAME_PREFIX), druidDataSource.getUsername());
assertEquals(properties.getProperty(DruidConfig.DB_CONFIG_PASSWORD_PREFIX), druidDataSource.getPassword());
}
@After
public void setConfigBack() {
if (bakConfigFile.exists()) {
System.out.println("恢复配置文件成功");
copyFile(bakConfigFile, configFile);
assertTrue(bakConfigFile.delete());
} else {
configFile.deleteOnExit();
}
}
@Before
public void recordConfig() {
File path = new File(LocalFileServiceImpl.getPath(System.getProperty(ConfigKeyEnum.BLOG_FILE_PATH.getKey())));
if (!path.exists() && !path.mkdirs()) {
fail("创建失败");
}
this.bakConfigFile = new File(DruidConfig.DB_CONFIG_PATH + ".bak");
this.configFile = new File(DruidConfig.DB_CONFIG_PATH);
if (configFile.exists()) {
System.out.println("备份文件成功");
copyFile(configFile, bakConfigFile);
}
when(this.env.getActiveProfiles()).thenReturn(new String[]{"dev"});
when(this.env.getProperty(DruidConfig.DB_CONFIG_PASSWORD_PREFIX)).thenReturn(globalEnvironment.getProperty(DruidConfig.DB_CONFIG_PASSWORD_PREFIX));
when(this.env.getProperty(DruidConfig.DB_CONFIG_URL_PREFIX)).thenReturn(globalEnvironment.getProperty(DruidConfig.DB_CONFIG_URL_PREFIX));
when(this.env.getProperty(DruidConfig.DB_CONFIG_USERNAME_PREFIX)).thenReturn(globalEnvironment.getProperty(DruidConfig.DB_CONFIG_USERNAME_PREFIX));
when(this.env.getProperty(DruidConfig.DB_CONFIG_DRIVER_CLASS_NAME_PREFIX)).thenReturn(globalEnvironment.getProperty(DruidConfig.DB_CONFIG_DRIVER_CLASS_NAME_PREFIX));
}
/**
* 复制文件
*
* @param src
* @param dist
* @return
*/
private boolean copyFile(File src, File dist) {
try {
// 复制备份文件
FileOutputStream fos = new FileOutputStream(dist);
FileInputStream fis = new FileInputStream(src);
int len = 0;
byte[] bytes = new byte[1024];
while ((len = fis.read(bytes)) != -1) {
fos.write(bytes, 0, len);
}
fos.close();
fis.close();
return true;
} catch (IOException e) {
return false;
}
}
}

View File

@@ -9,6 +9,7 @@ import cn.celess.blog.entity.request.LoginReq;
import cn.celess.blog.entity.request.UserReq;
import cn.celess.blog.mapper.UserMapper;
import cn.celess.blog.service.UserService;
import cn.celess.blog.service.FileService;
import cn.celess.blog.util.MD5Util;
import cn.celess.blog.util.RedisUtil;
import com.fasterxml.jackson.core.type.TypeReference;
@@ -115,7 +116,7 @@ public class UserControllerTest extends BaseTest {
assertNotNull(inputStream);
// mock 实现类
mockInjectInstance(userService, "qiniuService", new TestQiNiuServiceImpl());
mockInjectInstance(userService, "fileService", (FileService) TestFileManager::new);
MockMultipartFile file = new MockMultipartFile("file", "logo.png", MediaType.IMAGE_PNG_VALUE, inputStream);
getMockData(multipart("/user/imgUpload").file(file), userLogin()).andDo(result -> {

View File

@@ -0,0 +1,63 @@
package cn.celess.blog.mapper;
import cn.celess.blog.BaseTest;
import cn.celess.blog.entity.Config;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import static org.junit.Assert.*;
public class ConfigMapperTest extends BaseTest {
@Autowired
ConfigMapper configMapper;
@Test
public void getConfiguration() {
Config file = configMapper.getConfiguration("file.type");
assertNotNull(file);
assertNotNull(file.getId());
assertEquals("file.type", file.getName());
assertEquals("local", file.getValue());
}
@Test
public void updateConfiguration() {
Config config = generateConfig();
configMapper.addConfiguration(config);
assertNotNull(config.getId());
String s = randomStr();
config.setValue(s);
configMapper.updateConfiguration(config);
assertEquals(s, configMapper.getConfiguration(config.getName()).getValue());
}
@Test
public void getConfigurations() {
assertTrue(configMapper.getConfigurations().size() > 0);
}
@Test
public void addConfiguration() {
Config config = generateConfig();
configMapper.addConfiguration(config);
assertNotNull(config.getId());
}
@Test
public void deleteConfiguration() {
Config config = generateConfig();
configMapper.addConfiguration(config);
assertNotNull(config.getId());
assertNotEquals(0, configMapper.deleteConfiguration(config.getId()));
assertNull(configMapper.getConfiguration(config.getName()));
}
private Config generateConfig() {
Config config = new Config();
config.setName("test" + randomStr(4));
config.setValue(randomStr(4));
return config;
}
}

View File

@@ -0,0 +1,123 @@
package cn.celess.blog.service.fileserviceimpl;
import cn.celess.blog.BaseTest;
import cn.celess.blog.enmu.ConfigKeyEnum;
import cn.celess.blog.entity.model.FileInfo;
import cn.celess.blog.entity.model.FileResponse;
import cn.celess.blog.service.FileManager;
import cn.celess.blog.service.FileService;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.util.List;
import static org.junit.Assert.*;
@Slf4j
public class FileManagerTest extends BaseTest {
@Autowired
FileService fileService;
FileManager fileManager;
@Test
public void testUploadFile() {
// 测试本地的文件上传
fileManager = null;
System.setProperty(ConfigKeyEnum.FILE_TYPE.getKey(), "local");
fileManager = fileService.getFileManager();
assertTrue(fileManager instanceof LocalFileServiceImpl);
uploadFile();
// 测试七牛云的文件上传
fileManager = null;
System.setProperty(ConfigKeyEnum.FILE_TYPE.getKey(), "qiniu");
fileManager = fileService.getFileManager();
assertTrue(fileManager instanceof QiniuFileServiceImpl);
uploadFile();
}
@Test
public void testGetFileList() {
// 测试获取本地的文件列表
fileManager = null;
System.setProperty(ConfigKeyEnum.FILE_TYPE.getKey(), "local");
fileManager = fileService.getFileManager();
assertTrue(fileManager instanceof LocalFileServiceImpl);
getFileList();
// 测试获取七牛云的文件列表
fileManager = null;
System.setProperty(ConfigKeyEnum.FILE_TYPE.getKey(), "qiniu");
fileManager = fileService.getFileManager();
assertTrue(fileManager instanceof QiniuFileServiceImpl);
getFileList();
}
@Test
public void testDeleteFile() {
// 测试删除本地文件
fileManager = null;
System.setProperty(ConfigKeyEnum.FILE_TYPE.getKey(), "local");
fileManager = fileService.getFileManager();
assertTrue(fileManager instanceof LocalFileServiceImpl);
deleteFile();
// 测试删除七牛云文件
fileManager = null;
System.setProperty(ConfigKeyEnum.FILE_TYPE.getKey(), "qiniu");
fileManager = fileService.getFileManager();
assertTrue(fileManager instanceof QiniuFileServiceImpl);
deleteFile();
}
@SneakyThrows
public void uploadFile() {
String fileName = null;
File file = createFile();
FileResponse fileResponse = fileManager.uploadFile(new FileInputStream(file), file.getName());
assertEquals(file.getName(), fileResponse.key);
assertEquals(System.getProperty(ConfigKeyEnum.FILE_TYPE.getKey()), fileResponse.type);
// assertNotNull(fileResponse.hash);
fileName = fileResponse.key;
fileManager.deleteFile(fileName);
file.deleteOnExit();
}
public void getFileList() {
List<FileInfo> fileList = fileManager.getFileList();
fileList.forEach(fileInfo -> {
assertNotNull(fileInfo.key);
// assertNotNull(fileInfo.hash);
});
}
public void deleteFile() {
uploadFile();
}
@SneakyThrows
private File createFile() {
String fileName = "test." + randomStr(3);
File file = new File(fileName);
if (!file.exists() && file.createNewFile()) {
// 创建文件
log.debug("创建文件[{}]", fileName);
FileOutputStream outputStream = new FileOutputStream(file);
for (int i = 0; i < 100; i++) {
outputStream.write(new byte[1024]);
}
outputStream.flush();
outputStream.close();
}
return file;
}
}