679 Commits

Author SHA1 Message Date
dependabot[bot]
38d52c4609 Bump js-yaml from 3.14.1 to 3.14.2 (#875)
Bumps [js-yaml](https://github.com/nodeca/js-yaml) from 3.14.1 to 3.14.2.
- [Changelog](https://github.com/nodeca/js-yaml/blob/master/CHANGELOG.md)
- [Commits](https://github.com/nodeca/js-yaml/compare/3.14.1...3.14.2)

---
updated-dependencies:
- dependency-name: js-yaml
  dependency-version: 3.14.2
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-24 08:47:33 +08:00
dependabot[bot]
2b3f31a204 Bump cipher-base from 1.0.4 to 1.0.6 (#872)
Bumps [cipher-base](https://github.com/crypto-browserify/cipher-base) from 1.0.4 to 1.0.6.
- [Changelog](https://github.com/browserify/cipher-base/blob/master/CHANGELOG.md)
- [Commits](https://github.com/crypto-browserify/cipher-base/compare/v1.0.4...v1.0.6)

---
updated-dependencies:
- dependency-name: cipher-base
  dependency-version: 1.0.6
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-22 07:54:10 +08:00
dependabot[bot]
94332ee597 Bump sha.js from 2.4.11 to 2.4.12 (#873)
Bumps [sha.js](https://github.com/crypto-browserify/sha.js) from 2.4.11 to 2.4.12.
- [Changelog](https://github.com/browserify/sha.js/blob/master/CHANGELOG.md)
- [Commits](https://github.com/crypto-browserify/sha.js/compare/v2.4.11...v2.4.12)

---
updated-dependencies:
- dependency-name: sha.js
  dependency-version: 2.4.12
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-22 07:53:58 +08:00
dependabot[bot]
aa6dd044ba Bump form-data from 4.0.1 to 4.0.4 (#870)
Bumps [form-data](https://github.com/form-data/form-data) from 4.0.1 to 4.0.4.
- [Release notes](https://github.com/form-data/form-data/releases)
- [Changelog](https://github.com/form-data/form-data/blob/master/CHANGELOG.md)
- [Commits](https://github.com/form-data/form-data/compare/v4.0.1...v4.0.4)

---
updated-dependencies:
- dependency-name: form-data
  dependency-version: 4.0.4
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-07-23 08:25:56 +08:00
dependabot[bot]
aa9aa31c42 Bump on-headers and compression (#868)
Bumps [on-headers](https://github.com/jshttp/on-headers) and [compression](https://github.com/expressjs/compression). These dependencies needed to be updated together.

Updates `on-headers` from 1.0.2 to 1.1.0
- [Release notes](https://github.com/jshttp/on-headers/releases)
- [Changelog](https://github.com/jshttp/on-headers/blob/master/HISTORY.md)
- [Commits](https://github.com/jshttp/on-headers/compare/v1.0.2...v1.1.0)

Updates `compression` from 1.7.4 to 1.8.1
- [Release notes](https://github.com/expressjs/compression/releases)
- [Changelog](https://github.com/expressjs/compression/blob/master/HISTORY.md)
- [Commits](https://github.com/expressjs/compression/compare/1.7.4...v1.8.1)

---
updated-dependencies:
- dependency-name: on-headers
  dependency-version: 1.1.0
  dependency-type: indirect
- dependency-name: compression
  dependency-version: 1.8.1
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-07-21 11:09:24 +08:00
dependabot[bot]
bbdb1fb39d Bump brace-expansion from 1.1.11 to 1.1.12 (#866)
Bumps [brace-expansion](https://github.com/juliangruber/brace-expansion) from 1.1.11 to 1.1.12.
- [Release notes](https://github.com/juliangruber/brace-expansion/releases)
- [Commits](https://github.com/juliangruber/brace-expansion/compare/1.1.11...v1.1.12)

---
updated-dependencies:
- dependency-name: brace-expansion
  dependency-version: 1.1.12
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-07-02 10:00:46 +08:00
dependabot[bot]
b3f3594cd5 Bump pbkdf2 from 3.1.2 to 3.1.3 (#864)
Bumps [pbkdf2](https://github.com/crypto-browserify/pbkdf2) from 3.1.2 to 3.1.3.
- [Changelog](https://github.com/browserify/pbkdf2/blob/master/CHANGELOG.md)
- [Commits](https://github.com/crypto-browserify/pbkdf2/compare/v3.1.2...v3.1.3)

---
updated-dependencies:
- dependency-name: pbkdf2
  dependency-version: 3.1.3
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-06-29 09:51:03 +08:00
dependabot[bot]
c0504fe62f Bump http-proxy-middleware from 2.0.4 to 2.0.9 (#862) 2025-05-29 17:25:24 +08:00
dependabot[bot]
0bc23c796d Bump axios from 0.28.0 to 1.8.2 (#859) 2025-05-29 17:25:04 +08:00
zhendery
011b291e05 fix(水印): 添加水印后编码图片时漏掉了保存质量参数 (#856)
Co-authored-by: zhendery <zhendery@qq.com>
2025-02-15 02:36:56 +08:00
zhendery
effe173d9c fix(svg): svg也需要“缩略图”,复制一份svg到本地缩略图目录,否则从远程服务器拉取可能会比较慢。 (#852) 2025-01-08 06:33:12 +08:00
zhendery
bdd440d38f fix(图片上传): (小概率)修复图片“格式转换”时不同的图使用同一个临时文件导致变成同一张图的问题 (#851) 2025-01-08 06:32:50 +08:00
zhendery
fe5e4d9a67 feat: 添加svg矢量图的支持 (#833) 2024-12-28 20:47:52 +08:00
熊孝兵
66d7a0dce1 Update README.md 2024-12-28 20:46:24 +08:00
dependabot[bot]
4f13d7d5a1 Bump ip and webpack-dev-server (#848)
Removes [ip](https://github.com/indutny/node-ip). It's no longer used after updating ancestor dependency [webpack-dev-server](https://github.com/webpack/webpack-dev-server). These dependencies need to be updated together.


Removes `ip`

Updates `webpack-dev-server` from 4.7.4 to 4.15.2
- [Release notes](https://github.com/webpack/webpack-dev-server/releases)
- [Changelog](https://github.com/webpack/webpack-dev-server/blob/v4.15.2/CHANGELOG.md)
- [Commits](https://github.com/webpack/webpack-dev-server/compare/v4.7.4...v4.15.2)

---
updated-dependencies:
- dependency-name: ip
  dependency-type: indirect
- dependency-name: webpack-dev-server
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-12-28 20:25:19 +08:00
dependabot[bot]
093ef71c2c Bump aws/aws-sdk-php from 3.261.13 to 3.336.6 (#849)
Bumps [aws/aws-sdk-php](https://github.com/aws/aws-sdk-php) from 3.261.13 to 3.336.6.
- [Release notes](https://github.com/aws/aws-sdk-php/releases)
- [Commits](https://github.com/aws/aws-sdk-php/compare/3.261.13...3.336.6)

---
updated-dependencies:
- dependency-name: aws/aws-sdk-php
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-12-28 20:24:54 +08:00
dependabot[bot]
4a87a90d5b Bump browserify-sign from 4.2.1 to 4.2.3 (#847)
Bumps [browserify-sign](https://github.com/crypto-browserify/browserify-sign) from 4.2.1 to 4.2.3.
- [Changelog](https://github.com/browserify/browserify-sign/blob/main/CHANGELOG.md)
- [Commits](https://github.com/crypto-browserify/browserify-sign/compare/v4.2.1...v4.2.3)

---
updated-dependencies:
- dependency-name: browserify-sign
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-12-28 20:24:29 +08:00
dependabot[bot]
17cb901046 Bump @babel/traverse from 7.17.3 to 7.26.4 (#846)
Bumps [@babel/traverse](https://github.com/babel/babel/tree/HEAD/packages/babel-traverse) from 7.17.3 to 7.26.4.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.26.4/packages/babel-traverse)

---
updated-dependencies:
- dependency-name: "@babel/traverse"
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-12-28 20:24:16 +08:00
dependabot[bot]
c0ba47d47d Bump axios from 0.25.0 to 0.28.0 (#845)
Bumps [axios](https://github.com/axios/axios) from 0.25.0 to 0.28.0.
- [Release notes](https://github.com/axios/axios/releases)
- [Changelog](https://github.com/axios/axios/blob/v0.28.0/CHANGELOG.md)
- [Commits](https://github.com/axios/axios/compare/v0.25.0...v0.28.0)

---
updated-dependencies:
- dependency-name: axios
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-12-28 20:24:06 +08:00
dependabot[bot]
1d0cb15854 Bump follow-redirects from 1.14.9 to 1.15.9 (#844)
Bumps [follow-redirects](https://github.com/follow-redirects/follow-redirects) from 1.14.9 to 1.15.9.
- [Release notes](https://github.com/follow-redirects/follow-redirects/releases)
- [Commits](https://github.com/follow-redirects/follow-redirects/compare/v1.14.9...v1.15.9)

---
updated-dependencies:
- dependency-name: follow-redirects
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-12-28 20:23:51 +08:00
dependabot[bot]
465ff63230 Bump ws from 8.5.0 to 8.18.0 (#843)
Bumps [ws](https://github.com/websockets/ws) from 8.5.0 to 8.18.0.
- [Release notes](https://github.com/websockets/ws/releases)
- [Commits](https://github.com/websockets/ws/compare/8.5.0...8.18.0)

---
updated-dependencies:
- dependency-name: ws
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-12-28 20:23:37 +08:00
dependabot[bot]
04a421d3eb Bump webpack-dev-middleware from 5.3.1 to 5.3.4 (#842)
Bumps [webpack-dev-middleware](https://github.com/webpack/webpack-dev-middleware) from 5.3.1 to 5.3.4.
- [Release notes](https://github.com/webpack/webpack-dev-middleware/releases)
- [Changelog](https://github.com/webpack/webpack-dev-middleware/blob/v5.3.4/CHANGELOG.md)
- [Commits](https://github.com/webpack/webpack-dev-middleware/compare/v5.3.1...v5.3.4)

---
updated-dependencies:
- dependency-name: webpack-dev-middleware
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-12-28 20:23:26 +08:00
dependabot[bot]
2642719769 Bump webpack from 5.76.1 to 5.97.1 (#841)
Bumps [webpack](https://github.com/webpack/webpack) from 5.76.1 to 5.97.1.
- [Release notes](https://github.com/webpack/webpack/releases)
- [Commits](https://github.com/webpack/webpack/compare/v5.76.1...v5.97.1)

---
updated-dependencies:
- dependency-name: webpack
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-12-28 20:23:14 +08:00
dependabot[bot]
4de97fd91e Bump phpseclib/phpseclib from 3.0.19 to 3.0.43 (#840)
Bumps [phpseclib/phpseclib](https://github.com/phpseclib/phpseclib) from 3.0.19 to 3.0.43.
- [Release notes](https://github.com/phpseclib/phpseclib/releases)
- [Changelog](https://github.com/phpseclib/phpseclib/blob/master/CHANGELOG.md)
- [Commits](https://github.com/phpseclib/phpseclib/compare/3.0.19...3.0.43)

---
updated-dependencies:
- dependency-name: phpseclib/phpseclib
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-12-28 20:19:17 +08:00
dependabot[bot]
ee93913aac Bump path-to-regexp from 1.8.0 to 1.9.0 (#839)
Bumps [path-to-regexp](https://github.com/pillarjs/path-to-regexp) from 1.8.0 to 1.9.0.
- [Release notes](https://github.com/pillarjs/path-to-regexp/releases)
- [Changelog](https://github.com/pillarjs/path-to-regexp/blob/master/History.md)
- [Commits](https://github.com/pillarjs/path-to-regexp/compare/v1.8.0...v1.9.0)

---
updated-dependencies:
- dependency-name: path-to-regexp
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-12-28 20:18:59 +08:00
dependabot[bot]
e54c4e551c Bump league/commonmark from 2.3.9 to 2.6.0 (#834)
Bumps [league/commonmark](https://github.com/thephpleague/commonmark) from 2.3.9 to 2.6.0.
- [Release notes](https://github.com/thephpleague/commonmark/releases)
- [Changelog](https://github.com/thephpleague/commonmark/blob/2.6/CHANGELOG.md)
- [Commits](https://github.com/thephpleague/commonmark/compare/2.3.9...2.6.0)

---
updated-dependencies:
- dependency-name: league/commonmark
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-12-28 20:18:45 +08:00
dependabot[bot]
743409726f Bump send and express (#835)
Bumps [send](https://github.com/pillarjs/send) and [express](https://github.com/expressjs/express). These dependencies needed to be updated together.

Updates `send` from 0.17.2 to 0.19.0
- [Release notes](https://github.com/pillarjs/send/releases)
- [Changelog](https://github.com/pillarjs/send/blob/master/HISTORY.md)
- [Commits](https://github.com/pillarjs/send/compare/0.17.2...0.19.0)

Updates `express` from 4.17.3 to 4.21.2
- [Release notes](https://github.com/expressjs/express/releases)
- [Changelog](https://github.com/expressjs/express/blob/4.21.2/History.md)
- [Commits](https://github.com/expressjs/express/compare/4.17.3...4.21.2)

---
updated-dependencies:
- dependency-name: send
  dependency-type: indirect
- dependency-name: express
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-12-28 20:18:07 +08:00
dependabot[bot]
2fa82cc231 Bump elliptic from 6.5.4 to 6.6.1 (#836)
Bumps [elliptic](https://github.com/indutny/elliptic) from 6.5.4 to 6.6.1.
- [Commits](https://github.com/indutny/elliptic/compare/v6.5.4...v6.6.1)

---
updated-dependencies:
- dependency-name: elliptic
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-12-28 20:17:56 +08:00
dependabot[bot]
dd4c40d846 Bump cookie and express (#837)
Bumps [cookie](https://github.com/jshttp/cookie) and [express](https://github.com/expressjs/express). These dependencies needed to be updated together.

Updates `cookie` from 0.4.2 to 0.7.1
- [Release notes](https://github.com/jshttp/cookie/releases)
- [Commits](https://github.com/jshttp/cookie/compare/v0.4.2...v0.7.1)

Updates `express` from 4.17.3 to 4.21.2
- [Release notes](https://github.com/expressjs/express/releases)
- [Changelog](https://github.com/expressjs/express/blob/4.21.2/History.md)
- [Commits](https://github.com/expressjs/express/compare/4.17.3...4.21.2)

---
updated-dependencies:
- dependency-name: cookie
  dependency-type: indirect
- dependency-name: express
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-12-28 20:17:45 +08:00
dependabot[bot]
8acf78dac4 Bump laravel/framework from 9.52.4 to 9.52.17 (#838)
Bumps [laravel/framework](https://github.com/laravel/framework) from 9.52.4 to 9.52.17.
- [Release notes](https://github.com/laravel/framework/releases)
- [Changelog](https://github.com/laravel/framework/blob/11.x/CHANGELOG.md)
- [Commits](https://github.com/laravel/framework/compare/v9.52.4...v9.52.17)

---
updated-dependencies:
- dependency-name: laravel/framework
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-12-28 20:17:30 +08:00
dependabot[bot]
eb7c051512 Bump postcss from 8.4.12 to 8.4.31 (#723)
Bumps [postcss](https://github.com/postcss/postcss) from 8.4.12 to 8.4.31.
- [Release notes](https://github.com/postcss/postcss/releases)
- [Changelog](https://github.com/postcss/postcss/blob/main/CHANGELOG.md)
- [Commits](https://github.com/postcss/postcss/compare/8.4.12...8.4.31)

---
updated-dependencies:
- dependency-name: postcss
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-12-28 20:14:05 +08:00
dependabot[bot]
5ec90a08a1 Bump semver from 5.7.1 to 5.7.2 (#694)
Bumps [semver](https://github.com/npm/node-semver) from 5.7.1 to 5.7.2.
- [Release notes](https://github.com/npm/node-semver/releases)
- [Changelog](https://github.com/npm/node-semver/blob/v5.7.2/CHANGELOG.md)
- [Commits](https://github.com/npm/node-semver/compare/v5.7.1...v5.7.2)

---
updated-dependencies:
- dependency-name: semver
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-12-28 20:13:52 +08:00
dependabot[bot]
911275c13b Bump guzzlehttp/psr7 from 2.4.4 to 2.5.0 (#651)
Bumps [guzzlehttp/psr7](https://github.com/guzzle/psr7) from 2.4.4 to 2.5.0.
- [Release notes](https://github.com/guzzle/psr7/releases)
- [Changelog](https://github.com/guzzle/psr7/blob/2.5/CHANGELOG.md)
- [Commits](https://github.com/guzzle/psr7/compare/2.4.4...2.5.0)

---
updated-dependencies:
- dependency-name: guzzlehttp/psr7
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-05-16 17:55:13 +08:00
dependabot[bot]
704a15896d Bump laminas/laminas-diactoros from 2.24.0 to 2.25.2 (#657)
Bumps [laminas/laminas-diactoros](https://github.com/laminas/laminas-diactoros) from 2.24.0 to 2.25.2.
- [Release notes](https://github.com/laminas/laminas-diactoros/releases)
- [Commits](https://github.com/laminas/laminas-diactoros/compare/2.24.0...2.25.2)

---
updated-dependencies:
- dependency-name: laminas/laminas-diactoros
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-05-16 17:55:03 +08:00
熊孝兵
c80b92b346 后台图片管理支持通过图片路径名称搜索。Fixed #640 2023-03-30 09:49:01 +08:00
熊孝兵
67fc980968 Merge remote-tracking branch 'origin/master' 2023-03-29 11:11:45 +08:00
熊孝兵
05af7dffb8 后台图片管理支持通过图片路径名称搜索。Fixed #640 2023-03-29 11:11:37 +08:00
dependabot[bot]
ee068513c6 Bump webpack from 5.70.0 to 5.76.1 (#633)
Bumps [webpack](https://github.com/webpack/webpack) from 5.70.0 to 5.76.1.
- [Release notes](https://github.com/webpack/webpack/releases)
- [Commits](https://github.com/webpack/webpack/compare/v5.70.0...v5.76.1)

---
updated-dependencies:
- dependency-name: webpack
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-03-29 11:10:27 +08:00
熊孝兵
fbb022c877 改进 WebDav 以适配多种认证方式。Fixes #497, #520 2023-03-17 08:46:05 +08:00
dependabot[bot]
2b90d5eb75 Bump symfony/http-kernel from 6.0.11 to 6.0.20 (#608)
Bumps [symfony/http-kernel](https://github.com/symfony/http-kernel) from 6.0.11 to 6.0.20.
- [Release notes](https://github.com/symfony/http-kernel/releases)
- [Changelog](https://github.com/symfony/http-kernel/blob/6.2/CHANGELOG.md)
- [Commits](https://github.com/symfony/http-kernel/compare/v6.0.11...v6.0.20)

---
updated-dependencies:
- dependency-name: symfony/http-kernel
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-02-02 14:33:12 +08:00
dependabot[bot]
edb36f8a92 Bump json5 from 1.0.1 to 1.0.2 (#591)
Bumps [json5](https://github.com/json5/json5) from 1.0.1 to 1.0.2.
- [Release notes](https://github.com/json5/json5/releases)
- [Changelog](https://github.com/json5/json5/blob/main/CHANGELOG.md)
- [Commits](https://github.com/json5/json5/compare/v1.0.1...v1.0.2)

---
updated-dependencies:
- dependency-name: json5
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-18 14:26:33 +08:00
熊孝兵
a5bbb26ec8 Update README.md 2022-12-07 08:06:09 +08:00
dependabot[bot]
9394e590a8 Bump loader-utils from 1.4.1 to 1.4.2 (#564)
Bumps [loader-utils](https://github.com/webpack/loader-utils) from 1.4.1 to 1.4.2.
- [Release notes](https://github.com/webpack/loader-utils/releases)
- [Changelog](https://github.com/webpack/loader-utils/blob/v1.4.2/CHANGELOG.md)
- [Commits](https://github.com/webpack/loader-utils/compare/v1.4.1...v1.4.2)

---
updated-dependencies:
- dependency-name: loader-utils
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-11-18 09:26:51 +08:00
dependabot[bot]
0cb84a9662 Bump loader-utils from 1.4.0 to 1.4.1 (#561)
Bumps [loader-utils](https://github.com/webpack/loader-utils) from 1.4.0 to 1.4.1.
- [Release notes](https://github.com/webpack/loader-utils/releases)
- [Changelog](https://github.com/webpack/loader-utils/blob/v1.4.1/CHANGELOG.md)
- [Commits](https://github.com/webpack/loader-utils/compare/v1.4.0...v1.4.1)

---
updated-dependencies:
- dependency-name: loader-utils
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-11-09 10:45:22 +08:00
Wisp X
bb37a11bfb 降低缩略图质量 2022-10-21 10:25:47 +08:00
Jerry
d5230acdb3 修复前端图片水印路径提示错误问题 (#546)
* 修复前端水印上传目录提示错误的问题
e.g. 本应放在 /storage/app/public 目录下
     但前端提示放在 /public 目录下

* 修复编辑角色组中图片水印存储位置提示不正确bug
2022-10-14 09:10:48 +08:00
Wisp X
c66e58353a 修复图片无法重命名的 bug (fixed #536) 2022-10-09 17:46:51 +08:00
Wisp X
6661f35a15 图片处理质量默认值改为 75% 2022-10-09 10:18:52 +08:00
Jerry
5a8eb833be 修复前端水印上传目录提示错误的问题 (#540)
e.g. 本应放在 /storage/app/public 目录下
     但前端提示放在 /public 目录下
2022-09-28 08:41:10 +08:00
Wisp X
1925a1ae7a minio 策略增加 bucket_endpoint 选项配置 2022-09-20 09:01:49 +08:00
Frank Qing
bb9ef6e267 Support minio https url (#534) 2022-09-20 08:23:02 +08:00
Wisp X
923f567e0a 升级拓展 2022-08-17 13:36:19 +08:00
Wisp X
62809f5300 改进升级方式 2022-08-17 13:06:14 +08:00
Wisp X
c46207c278 支持使用角色组控制上传图片的质量于格式 (Closed #415) 2022-08-17 11:35:05 +08:00
Wisp X
289ecffbb1 上传时及时销毁 InterventionImage 实例以节省内存。 2022-08-17 10:48:54 +08:00
Wisp X
2fdb10a60f 改进阿里云 oss client 连接方式 2022-08-17 10:37:53 +08:00
Wisp X
b2b1693a2f 公告支持手动打开 (Closed #504) 2022-08-17 10:28:28 +08:00
Wisp X
b6f5f2405b 修复角色组设置水印模式,无法选择「动态生成」的 bug (Fixed #502) 2022-08-17 10:09:52 +08:00
Wisp X
f49f52dc34 修改接口文档文案 2022-08-17 10:04:21 +08:00
Wisp X
cd1436add2 update packages 2022-08-17 10:00:07 +08:00
Wisp X
aefbbda2bf Merge remote-tracking branch 'origin/master' 2022-08-05 17:49:49 +08:00
Wisp X
768ca9f24a 修复使用原文件名规则时返回了错误链接的 bug.(Fixed #496) 2022-08-05 17:35:10 +08:00
dependabot[bot]
fb9c390bc7 Bump terser from 4.8.0 to 4.8.1 (#488)
Bumps [terser](https://github.com/terser/terser) from 4.8.0 to 4.8.1.
- [Release notes](https://github.com/terser/terser/releases)
- [Changelog](https://github.com/terser/terser/blob/master/CHANGELOG.md)
- [Commits](https://github.com/terser/terser/commits)

---
updated-dependencies:
- dependency-name: terser
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-07-21 08:10:56 +08:00
Wisp X
a4a60f13a0 修复文件名含有特殊字符导致排版异常的 bug.(Fixed #471) 2022-07-11 14:13:46 +08:00
TeacherDu
5071c3a904 fix a bug (#474)
* fix a bug

https://github.com/lsky-org/lsky-pro/issues/473

* Update index.blade.php

Co-authored-by: Wisp X <wisp-x@qq.com>
2022-07-11 08:06:10 +08:00
dependabot[bot]
2c52c87c95 Bump guzzlehttp/guzzle from 7.4.4 to 7.4.5 (#453)
Bumps [guzzlehttp/guzzle](https://github.com/guzzle/guzzle) from 7.4.4 to 7.4.5.
- [Release notes](https://github.com/guzzle/guzzle/releases)
- [Changelog](https://github.com/guzzle/guzzle/blob/master/CHANGELOG.md)
- [Commits](https://github.com/guzzle/guzzle/compare/7.4.4...7.4.5)

---
updated-dependencies:
- dependency-name: guzzlehttp/guzzle
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-06-22 08:31:33 +08:00
Wisp X
3161394a83 改进 dropdown 组件 2022-06-15 10:32:15 +08:00
Wisp X
8f6da67f4f 改进 dropdown 组件 2022-06-15 10:30:53 +08:00
Wisp X
4d3bfed846 Merge remote-tracking branch 'origin/master' 2022-06-15 10:19:00 +08:00
Wisp X
2086daf1ae 改进公告弹窗 Closed #447 2022-06-15 10:17:55 +08:00
dependabot[bot]
a8ab0c6680 Bump guzzlehttp/guzzle from 7.4.3 to 7.4.4 (#445)
Bumps [guzzlehttp/guzzle](https://github.com/guzzle/guzzle) from 7.4.3 to 7.4.4.
- [Release notes](https://github.com/guzzle/guzzle/releases)
- [Changelog](https://github.com/guzzle/guzzle/blob/master/CHANGELOG.md)
- [Commits](https://github.com/guzzle/guzzle/compare/7.4.3...7.4.4)

---
updated-dependencies:
- dependency-name: guzzlehttp/guzzle
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-06-10 14:53:04 +08:00
Wisp X
8b5bf74aa2 修复新用户注册后 IP 为空的 bug. 2022-06-02 08:10:24 +08:00
Wisp X
c0513f85a1 支持设置图片 url 额外参数 Closed #435 2022-05-30 11:14:32 +08:00
dependabot[bot]
8f0db089af Bump guzzlehttp/guzzle from 7.4.2 to 7.4.3 (#434)
Bumps [guzzlehttp/guzzle](https://github.com/guzzle/guzzle) from 7.4.2 to 7.4.3.
- [Release notes](https://github.com/guzzle/guzzle/releases)
- [Changelog](https://github.com/guzzle/guzzle/blob/master/CHANGELOG.md)
- [Commits](https://github.com/guzzle/guzzle/compare/7.4.2...7.4.3)

---
updated-dependencies:
- dependency-name: guzzlehttp/guzzle
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-05-26 06:49:04 +08:00
Wisp X
d705542f44 fix a bug 2022-05-23 17:38:05 +08:00
Wisp X
4266fd60b7 水印支持切换为覆盖原图或动态生成 2022-05-23 17:28:45 +08:00
Wisp X
315b5b8296 改进样式 2022-05-20 11:28:42 +08:00
Wisp X
10699d69f6 fix a bug 2022-05-20 09:09:40 +08:00
Wisp X
58da33d3c9 取消索引创建迁移文件 2022-05-17 16:42:25 +08:00
Wisp X
20871efb5c 改进 2022-05-11 15:27:57 +08:00
Wisp X
912e3747ee fix a bug 2022-05-10 13:22:55 +08:00
Wisp X
fba0d77e7b fix a bug 2022-05-07 09:56:55 +08:00
Wisp X
10dea5cffd fix a bug 2022-05-06 09:30:54 +08:00
Wisp X
f7983a2f21 修复PC端开启平板模式后无法响应触摸事件的问题 Fixed #386 2022-04-29 11:16:50 +08:00
Wisp X
8ab428c593 改进样式 2022-04-29 09:47:17 +08:00
Wisp X
fb5f83f43c push version 2022-04-27 17:34:10 +08:00
Wisp X
73750890d2 build style 2022-04-27 17:32:27 +08:00
Wisp X
3ad7e711be build style 2022-04-27 17:01:50 +08:00
Wisp X
b6fd41841b 更新版本号 2022-04-27 16:59:33 +08:00
Wisp X
b61db2b31a fix a bug 2022-04-27 16:54:45 +08:00
Wisp X
cff42566c8 fix a bug 2022-04-27 16:30:44 +08:00
Wisp X
e44fabbd31 给图片表增加索引 2022-04-27 15:58:58 +08:00
Wisp X
3cdf495bef 给图片表增加索引 2022-04-27 15:53:52 +08:00
Wisp X
6291acbcee 给图片表增加索引 2022-04-27 15:51:53 +08:00
Wisp X
1434d6fa1c 改进 2022-04-27 15:34:41 +08:00
Wisp X
73c55b1a77 修复图片量太大导致内存溢出的 bug 2022-04-27 14:52:48 +08:00
TeacherDu
8780a21fe7 gravatar国内的节点 (#399) 2022-04-24 08:07:58 +08:00
Wisp X
3acabd7b38 fix a bug. 2022-04-21 14:14:34 +08:00
Wisp X
1f826fd652 默认信任所有代理 2022-04-19 17:10:11 +08:00
Wisp X
8d266b3d5a update packages. 2022-04-19 16:42:08 +08:00
Wisp X
02b9695882 修复时间不正确的 bug 2022-04-19 11:13:03 +08:00
Wisp X
5bba6d0d52 移除 alpinejs 控制台警告 2022-04-12 14:37:23 +08:00
Wisp X
52f9c0cbe8 update packages 2022-04-07 14:36:08 +08:00
Wisp X
d2b4910196 删除文件时同步删除缓存 2022-04-07 08:27:52 +08:00
Wisp X
9a5cbb2ea7 fix a bug 2022-04-06 08:45:54 +08:00
Wisp X
b9a3ea2c44 fix a bug 2022-04-06 08:19:51 +08:00
Wisp X
0d826042ad 更新版本号 2022-04-01 17:12:26 +08:00
Wisp X
f9a84c149c 增加一些 tips 2022-04-01 16:44:40 +08:00
Wisp X
798e72f634 适配路径支持设置为空 2022-04-01 16:20:23 +08:00
Wisp X
dec4e6fdb7 修复通过请求删除接口无法删除物理文件的 bug. Closed #366 2022-04-01 16:08:37 +08:00
Wisp X
3cecc20f44 改进首页底部在手机端的样式 2022-03-31 08:30:37 +08:00
Wisp X
86d67450ea 修复站点名称过长导致手机端侧边栏无法收起的 bug 2022-03-30 17:10:43 +08:00
Wisp X
aa9887715a 修复我的图片中添加/修改项目时回车出错的 bug. Closed 363 2022-03-30 16:26:11 +08:00
Wisp X
fc1c2095a6 up 2022-03-30 08:01:15 +08:00
Q-Y-H
a0d216fb2d 路径命名可为空 (#364) 2022-03-30 07:58:23 +08:00
Wisp X
b8471f2ca2 fix a bug 2022-03-25 08:48:02 +08:00
Wisp X
b7601272dd fix a bug 2022-03-25 08:41:06 +08:00
Wisp X
184dd51122 update README.md 2022-03-24 15:15:33 +08:00
Wisp X
3df02550c4 update README.md 2022-03-24 10:32:46 +08:00
Wisp X
f9b046a32f 图片审核跳过 ico 格式的图片 2022-03-24 10:25:34 +08:00
Wisp X
01c850739a update packages 2022-03-24 10:13:51 +08:00
Wisp X
43bb7792ac 增加 nsfwjs 图片审核驱动 Closed #347 2022-03-24 10:07:25 +08:00
Wisp X
6c31ace31a update packages 2022-03-24 07:55:51 +08:00
Wisp X
e9050f4b9b fix a bug 2022-03-23 17:40:06 +08:00
Wisp X
a2a45e72ba fix a bug 2022-03-23 17:31:03 +08:00
Wisp X
317c2ea369 Merge branch 'dev' 2022-03-23 17:28:14 +08:00
Wisp X
df2ed46ab2 fix 2022-03-23 17:27:51 +08:00
Wisp X
979b628f59 fix 2022-03-23 17:15:24 +08:00
Wisp X
71924b0397 支持腾讯云图片审核 2022-03-23 17:00:47 +08:00
Wisp X
b33c309a56 鉴黄跳过 psd、tif 格式的图片 2022-03-23 15:55:46 +08:00
Wisp X
ae785cc56f 支持设置粘贴图片后的动作 2022-03-23 15:45:36 +08:00
Wisp X
66423b1d18 上传时预览图片支持点击查看大图 2022-03-23 15:02:48 +08:00
Wisp X
ab21fb203f 改进升级方式 2022-03-23 13:09:15 +08:00
Wisp X
31503d340a 改进升级方式 2022-03-23 12:26:01 +08:00
Wisp X
165635bce9 改进升级方式 2022-03-23 12:25:42 +08:00
Wisp X
45a639fe3e Merge branch 'dev' 2022-03-22 17:35:32 +08:00
Wisp X
b22a334b86 format code 2022-03-22 17:35:26 +08:00
Wisp X
4be12d828d fix a bug 2022-03-22 17:34:53 +08:00
Wisp X
683a0a4888 修复翻页后搜索条件丢失的 bug Closed #343 2022-03-22 17:34:08 +08:00
Wisp X
769eb4bbd5 改进 2022-03-22 14:17:21 +08:00
Wisp X
10921b34fc update README.md 2022-03-22 14:10:47 +08:00
Wisp X
ede1ec5344 图片管理列表,psd、tiff 格式的图片预览时使用缩略图 #339 2022-03-22 08:13:41 +08:00
Wisp X
6f6403165c 图片管理列表,psd、tiff 格式的图片预览时使用缩略图 #339 2022-03-22 08:13:12 +08:00
Pumpkin
43b54a05ca 为底部ICP备案号链接添加target属性“_blank” (#340) 2022-03-22 06:27:33 +08:00
Wisp X
b1b5bd0782 增加在线更新超时时间 2022-03-21 14:24:05 +08:00
Wisp X
b2a4c5b971 增加在线更新超时时间 2022-03-21 14:23:19 +08:00
Wisp X
b86d5053f2 upgrade packages 2022-03-21 13:11:27 +08:00
Wisp X
aa4fd1c80a update version 2022-03-21 13:07:35 +08:00
Wisp X
ed48d6ca75 fix a bug 2022-03-21 11:35:47 +08:00
Wisp X
97a8c74e22 rollback 2022-03-21 10:02:53 +08:00
Wisp X
8d79647974 s3 支持设置 endpoint 2022-03-21 09:54:01 +08:00
Wisp X
07cd115048 增加安装要求 2022-03-21 09:40:20 +08:00
Wisp X
e227e5acd6 上传时设置文件以及文件夹可见性 2022-03-21 09:38:34 +08:00
Wisp X
2fb3eba233 上传时设置文件以及文件夹可见性 2022-03-21 09:36:02 +08:00
Wisp X
dde0e988f8 Merge branch 'master' into dev 2022-03-21 08:11:55 +08:00
梦彗業
32a9c2e85c 为底部ICP备案号添加工信部跳转 (#334) 2022-03-21 08:09:10 +08:00
Wisp X
5bd4e81ec5 在线更新后清除缓存 2022-03-18 15:40:20 +08:00
Wisp X
d428d535f5 删除本地策略时同时删除符号连接 2022-03-18 15:13:18 +08:00
Wisp X
3b1d566973 水印合成跳过 gif、ico 格式图片 2022-03-18 09:14:17 +08:00
Wisp X
659afb6a50 ico 格式图片在开启原图保护的情况下直接输出,不经过 InterventionImage 处理 2022-03-18 09:11:45 +08:00
Wisp X
59c490b5d3 修复 ico 以及部分带有动画的 webp 格式图片无法上传的 bug.
获取图片宽高使用 getimagesize 而非 imagick 以获取更好的兼容性和性能
2022-03-18 08:17:01 +08:00
Wisp X
f1af71d3f3 粘贴上传文件名改为时间戳 Closed #314 2022-03-17 09:14:30 +08:00
Wisp X
2511fd6abb 删除文件图片时同时删除缩略图 2022-03-16 21:11:02 +08:00
Wisp X
b2663b8ec0 修复我的图片页面删除图片后列表为空的bug 2022-03-16 17:55:59 +08:00
Wisp X
eee6346bec 修复管理后台无法冻结用户的bug Fixed #328 2022-03-16 11:13:59 +08:00
Wisp X
6edee6b783 update configs 2022-03-16 11:10:02 +08:00
Wisp X
e526d0b4c8 update composer.json 2022-03-16 10:10:03 +08:00
Wisp X
37c6ccd29d compatible swoole 2022-03-16 10:00:58 +08:00
Wisp X
b0c6a6defa add swoole options 2022-03-16 09:30:28 +08:00
Wisp X
3badd7f7a3 add extension laravel/octane 2022-03-16 09:01:56 +08:00
Wisp X
96ba030b03 修复细节错误 2022-03-15 08:58:42 +08:00
Wisp X
54d421f93b 修复文字水印字体颜色无法修改的bug 2022-03-15 08:55:44 +08:00
Wisp X
9495179f96 fix a bug 2022-03-15 08:45:12 +08:00
Wisp X
32f629bac9 改进 2022-03-15 08:42:11 +08:00
Wisp X
a9650b6780 Minio 增加区域配置 2022-03-15 08:03:55 +08:00
Wisp X
08e016e8b0 修复打开弹框时公告被同时打开的bug 2022-03-14 14:05:50 +08:00
Wisp X
bcffb4c8fc fix a bug 2022-03-14 11:44:54 +08:00
Wisp X
985af20b5b remove thumbnail route 2022-03-14 11:38:08 +08:00
Wisp X
0c8e4f20c3 角色组增加 tips 2022-03-14 10:47:04 +08:00
Wisp X
86cc72f27a fix a bug 2022-03-14 10:40:34 +08:00
Wisp X
bf26cb6699 增加生成缩略图命令 2022-03-14 10:35:41 +08:00
Wisp X
928727c149 修复关闭游客上传后公告无法弹出的问题 2022-03-14 10:17:15 +08:00
Wisp X
db9d878fe2 更改缩略图生成逻辑 2022-03-14 10:00:07 +08:00
Wisp X
8663fb4134 修复图片不属于任何策略时无法查看详情的bug 2022-03-14 08:12:30 +08:00
Wisp X
7464decb8e fix a bug 2022-03-13 20:02:26 +08:00
Wisp X
7d5df4cfba remove polyfilljs 2022-03-13 19:58:54 +08:00
Wisp X
03f0544ff4 修复删除策略或删除组导致500错误的bug 2022-03-13 18:34:11 +08:00
Wisp X
ca5a215fc9 修复 psd、tif 格式图片无法上传的bug 2022-03-13 17:29:13 +08:00
Wisp X
4ba44d7a16 add polyfill 2022-03-13 16:08:32 +08:00
Wisp X
476d797191 修复首次进入首页上传时提示未知策略的bug 2022-03-13 15:50:00 +08:00
Wisp X
1714e7c876 修复图片宽高过小导致我的图片页面展示异常的bug 2022-03-13 15:08:53 +08:00
Wisp X
a3acf85199 修复管理员修改用户密码将明文入库的bug 2022-03-13 15:05:37 +08:00
Wisp X
6d0631b494 upgrade packages 2022-03-13 13:23:17 +08:00
Wisp X
d465172509 修复备案号不显示的 bug 2022-03-13 13:20:32 +08:00
Wisp X
f2089c96ca fix a bug 2022-03-13 13:17:36 +08:00
Wisp X
e9e2a1eb0e 修复无法上传大写拓展名的文件 2022-03-13 13:11:15 +08:00
Wisp X
51b2682dd0 登录页面增加注册超链接 2022-03-13 12:24:58 +08:00
Wisp X
d0bd143e17 修复后台图片管理中图片大小显示错误的bug 2022-03-13 11:52:02 +08:00
Wisp X
aab42fb630 忽略默认符号连接 2022-03-13 09:54:40 +08:00
Wisp X
2389f7b39b 修复相册图片不能加载更多的bug 2022-03-13 09:47:12 +08:00
Wisp X
503b7d8249 fix a bug 2022-03-13 03:19:00 +08:00
Wisp X
87f7c500c1 fix a bug 2022-03-13 03:15:39 +08:00
Wisp X
69209f75d3 修复创建相册后用户相册数量未改变的bug 2022-03-13 02:17:23 +08:00
Wisp X
8b2bae00d8 修复我的图片删除后选中菜单未消失的bug 2022-03-13 02:08:43 +08:00
Wisp X
cf8516d889 图片上传服务取消数据库事务使用,避免并发上传时出现死锁。 2022-03-13 02:02:03 +08:00
Wisp X
435abde829 修复上传 jpeg 格式的图片被重命名为 jpg 的 bug 2022-03-12 23:58:05 +08:00
Wisp X
7d5cd6c825 fix a bug 2022-03-12 23:36:24 +08:00
Wisp X
47db995a54 Merge branch 'master' into dev 2022-03-12 21:30:02 +08:00
Wisp X
de11905441 hello lsky-pro 2.0 2022-03-12 20:52:19 +08:00
Wisp X
4ee82c37b0 hello lsky-pro 2.0 Closed #281 2022-03-12 20:49:15 +08:00
Wisp X
4bf87a11c9 增加安装条件 2022-03-12 19:16:21 +08:00
Wisp X
bf668edcae update package 2022-03-12 17:57:21 +08:00
Wisp X
ac62a5613a 关闭游客上传直接重定向至登录页面 2022-03-12 17:40:03 +08:00
Wisp X
abc0f70503 动态改变进度条颜色 2022-03-12 17:30:41 +08:00
Wisp X
5781077cfa 修复缩略图中带有大写的拓展名导致无法输出图片的bug 2022-03-12 16:31:00 +08:00
Wisp X
90cb1ec918 update README.md 2022-03-12 14:19:00 +08:00
Wisp X
485b326c96 原图保护增加清除缓存功能 2022-03-12 11:12:10 +08:00
Wisp X
45021411ae 增加安装条件 2022-03-12 09:49:17 +08:00
Wisp X
58c34a2548 upgrade package 2022-03-11 23:40:37 +08:00
Wisp X
39f1bb34f0 表格增加滚动条 2022-03-11 23:12:00 +08:00
Wisp X
5c4c1b6a65 修复选择多个文件时某个文件未通过校验导致添加到队列失败的 bug 2022-03-11 22:43:14 +08:00
Wisp X
5d29e7e0d1 fix a bug 2022-03-11 22:02:32 +08:00
Wisp X
89652c0ae3 add license 2022-03-10 17:29:35 +08:00
Wisp X
a96e5e099c upgrade package 2022-03-10 13:18:49 +08:00
Wisp X
577b904aa0 fix a bug 2022-03-10 12:22:32 +08:00
Wisp X
0b5abe9b05 update package 2022-03-10 11:34:44 +08:00
Wisp X
1053fccc9b 适配又拍云储存策略 2022-03-10 10:15:06 +08:00
Wisp X
4795c74724 fix a bugs 2022-03-09 08:19:28 +08:00
Wisp X
8f611098f7 update README.md 2022-03-08 21:13:50 +08:00
Wisp X
39ad13cbad update packages 2022-03-08 21:04:01 +08:00
Wisp X
eda5d2dd90 update packages 2022-03-08 20:50:23 +08:00
Wisp X
f9d67996e5 更新 README.md 2022-03-08 13:14:19 +08:00
Wisp X
34541421ed 更新 README.md 2022-03-08 13:07:49 +08:00
Wisp X
a9eacc24d2 更新 README.md 2022-03-08 13:01:17 +08:00
Wisp X
eae7321de3 更新 README.md 2022-03-08 12:36:21 +08:00
Wisp X
16b9ea1dbb 更新 README.md 2022-03-08 11:35:02 +08:00
Wisp X
c623535109 🐛 Fixing a bug. 2022-03-07 11:19:12 +08:00
Wisp X
9996af93f8 🐛 Fixing a bug. 2022-03-07 11:09:22 +08:00
Wisp X
43f8900cdd 🎨 改进结构 2022-03-07 08:19:43 +08:00
Wisp X
cebf64a4bb 🐛 修复 BUG 2022-03-06 21:32:41 +08:00
Wisp X
4ea2f28fd4 配置 workflows 2022-03-06 21:01:26 +08:00
Wisp X
a63527c086 配置 workflows 2022-03-06 20:37:21 +08:00
Wisp X
f725dce3a9 🐛 修复 BUG 2022-03-06 20:22:58 +08:00
Wisp X
134ceefe19 配置 workflows 2022-03-06 20:01:35 +08:00
Wisp X
7cd5a84f33 配置 workflows 2022-03-06 19:59:49 +08:00
Wisp X
af46c54b15 配置 workflows 2022-03-06 19:23:59 +08:00
Wisp X
ef08cf6fa8 配置 workflows 2022-03-06 18:02:44 +08:00
Wisp X
2bd6f6c23d 配置 workflows 2022-03-06 17:58:09 +08:00
Wisp X
f9f5d121c2 🐛 修复 BUG 2022-03-06 17:25:06 +08:00
Wisp X
338ade3090 🐛 修复 BUG 2022-03-06 17:22:19 +08:00
Wisp X
65e14f78f3 🐛 修复 BUG 2022-03-06 15:36:34 +08:00
Wisp X
f7982d89e8 增加命名规则对照表 2022-03-06 15:24:32 +08:00
Wisp X
e273b30e08 🐛 修复 BUG 2022-03-06 14:17:02 +08:00
Wisp X
677a0afeee 增加策略列表接口,接口支持上传时指定策略 2022-03-06 14:11:30 +08:00
Wisp X
0d1fd3461a 🐛 修复 BUG 2022-03-06 13:47:25 +08:00
Wisp X
551ee7014c 支持上传时动态切换储存策略 2022-03-06 13:44:11 +08:00
Wisp X
b74049bb37 🐛 修复 BUG 2022-03-06 12:15:55 +08:00
Wisp X
67ff633a3f 增加自定义js的支持 2022-03-06 11:30:05 +08:00
Wisp X
b69bc02b27 改进安装程序 2022-03-05 23:53:31 +08:00
Wisp X
0a8bfe6bf1 改进安装程序 2022-03-05 23:41:47 +08:00
Wisp X
a2daf5a404 🐛 修复 BUG 2022-03-05 23:26:02 +08:00
Wisp X
b582dd9b3e 增加安装异常的日志 2022-03-05 23:05:37 +08:00
Wisp X
c30e1a96aa 增加安装异常的日志 2022-03-05 22:46:30 +08:00
Wisp X
162900a9c5 🐛 修复 BUG 2022-03-05 21:41:52 +08:00
Wisp X
e1036e8fe7 🐛 修复 BUG 2022-03-05 21:39:25 +08:00
Wisp X
23678302ba 🐛 修复 BUG 2022-03-05 21:34:47 +08:00
Wisp X
9c7920049f 增加 minio 策略的支持 2022-03-05 20:51:11 +08:00
Wisp X
072f4bff70 完成 s3 储存策略 2022-03-05 19:59:42 +08:00
Wisp X
b7daf09bed 📦 Updating compiled files or packages. 2022-03-05 15:42:02 +08:00
Wisp X
6f80343706 完成阿里云oss储存策略 2022-03-05 15:31:32 +08:00
Wisp X
7f83aa828a 完成 webdav 储存方式 2022-03-05 15:00:07 +08:00
Wisp X
aed749a86d 完成 ftp 储存方式 2022-03-05 13:08:53 +08:00
Wisp X
68a1a8664a 完成sftp储存策略 2022-03-05 12:10:10 +08:00
Wisp X
2a7dd080ef 完善腾讯云储存策略 2022-03-05 10:01:58 +08:00
Wisp X
df52d48a31 完成腾讯云cos储存策略 2022-03-05 09:50:44 +08:00
Wisp X
4620003d9a 🐛 修复 BUG 2022-03-05 01:52:55 +08:00
Wisp X
a43df045d7 🐛 修复 BUG 2022-03-05 01:51:00 +08:00
Wisp X
43b5b09714 支持修改用户的角色组 2022-03-05 01:47:31 +08:00
Wisp X
7c4865ade9 七牛云储存策略 2022-03-05 01:39:28 +08:00
Wisp X
3f05eff482 更新 README.md 2022-03-04 18:30:53 +08:00
Wisp X
414a187bea 📦 Updating compiled files or packages. 2022-03-04 18:21:19 +08:00
Wisp X
d987ebda63 更新 ico 图标 2022-03-04 17:13:00 +08:00
Wisp X
ca4084fc35 更新 README.md 2022-03-04 16:59:39 +08:00
Wisp X
fe3ed2eefd 更新 logo 2022-03-04 16:26:56 +08:00
Wisp X
8f037bdde0 增加用户uid命名规则 2022-03-04 16:06:57 +08:00
Wisp X
46c1f7b911 改进升级方式 2022-03-04 15:22:04 +08:00
Wisp X
d47109fd5c 完成更新程序 2022-03-04 14:25:56 +08:00
Wisp X
d19eb061b1 🐛 修复 BUG 2022-03-03 11:35:37 +08:00
Wisp X
2bccbb72d8 💄 Updating the UI and style files. 2022-03-03 10:38:54 +08:00
Wisp X
a903680487 🐛 修复 BUG 2022-03-01 12:36:14 +08:00
Wisp X
ccddb8a0fd 🐛 修复 BUG 2022-03-01 12:33:55 +08:00
Wisp X
234defc719 🐛 修复 BUG 2022-03-01 12:19:28 +08:00
Wisp X
2e09013bb7 改进 2022-03-01 11:51:23 +08:00
Wisp X
843abacdef 适配游客组能使用多个储存的特性 2022-03-01 11:39:51 +08:00
Wisp X
60a7d0a87d 💄 Updating the UI and style files. 2022-02-28 17:58:02 +08:00
Wisp X
86edc377d1 改进升级方式 2022-02-28 17:05:39 +08:00
Wisp X
dada4805c4 改进升级方式 2022-02-28 17:03:05 +08:00
Wisp X
694669187f Introducing new features. 2022-02-28 15:14:26 +08:00
Wisp X
9bf925cced 增加安装要求 2022-02-28 09:53:25 +08:00
Wisp X
cfc898bda8 改进升级页面样式 2022-02-28 09:14:15 +08:00
Wisp X
ebb352782f 系统更新页面布局 2022-02-26 17:46:25 +08:00
Wisp X
c108e9bc76 修复移动图片,相册数量更新错误的 bug. 2022-02-25 17:07:19 +08:00
Wisp X
4e59097013 增加首页公告功能 2022-02-25 15:53:22 +08:00
Wisp X
82a98a3374 💄 Updating the UI and style files. 2022-02-25 15:02:21 +08:00
Wisp X
9a178dd3de 🐛 修复 BUG 2022-02-25 14:56:06 +08:00
Wisp X
c8e84dcb62 💄 Updating the UI and style files. 2022-02-25 14:51:56 +08:00
Wisp X
5cc8320f14 🐛 修复 BUG 2022-02-25 14:49:01 +08:00
Wisp X
0bdad8b4fe 适配多数据库 2022-02-25 13:52:30 +08:00
Wisp X
b899a3bf08 适配多数据库 2022-02-25 11:09:35 +08:00
Wisp X
697aa5bb66 适配多数据库 2022-02-25 11:05:21 +08:00
Wisp X
27ccdb4c73 改进模型 2022-02-25 09:25:44 +08:00
Wisp X
c1de4d03d0 Introducing new features. 2022-02-25 09:04:05 +08:00
Wisp X
af0d3db299 🐛 修复 BUG 2022-02-25 08:57:47 +08:00
Wisp X
f3c50617b5 增加应用 url 配置 2022-02-25 08:50:14 +08:00
Wisp X
4b1779bf38 💄 显示违规图片标记 2022-02-25 08:19:28 +08:00
Wisp X
590d2b7361 完善图片审核功能 2022-02-24 18:48:30 +08:00
Wisp X
0c31926fd4 完善图片审核功能 2022-02-24 18:47:22 +08:00
Wisp X
b753f5fa0e 改进角色组编辑 2022-02-24 17:38:21 +08:00
Wisp X
8b23b16b5c 🐛 修复 BUG 2022-02-24 17:27:16 +08:00
Wisp X
ef45e36d4d 改进安装页面 2022-02-24 16:15:24 +08:00
Wisp X
6b04113f61 改进安装页面 2022-02-24 16:13:18 +08:00
Wisp X
6135e29936 改进图片管理 2022-02-24 15:58:48 +08:00
Wisp X
af9d787efe 完成安装程序。 2022-02-24 15:41:01 +08:00
Wisp X
6172fc0a8c 改进安装 2022-02-24 09:03:45 +08:00
Wisp X
ddca7a1a22 🔧 发布异常页面配置 2022-02-22 17:23:14 +08:00
Wisp X
aa122a394d 画廊页面 2022-02-22 15:31:23 +08:00
Wisp X
09600cf561 改进检测安装逻辑 2022-02-22 15:26:26 +08:00
Wisp X
53ae724620 完善安装命令 2022-02-22 08:48:37 +08:00
Wisp X
2d905a5550 数据库安装指令 2022-02-22 08:31:55 +08:00
Wisp X
144f2650a2 📦 Updating compiled files or packages. 2022-02-21 14:28:30 +08:00
Wisp X
4b9213f358 🐛 修复 BUG 2022-02-21 08:10:56 +08:00
Wisp X
7ad296b74b 完善接口文档 2022-02-20 17:29:24 +08:00
Wisp X
7f53c90128 个人信息接口 2022-02-20 17:13:27 +08:00
Wisp X
6f08b30986 删除相册接口 2022-02-20 16:50:36 +08:00
Wisp X
0f26dfd8a0 相册列表接口 2022-02-20 16:45:32 +08:00
Wisp X
cb0cce0c09 图片删除接口 2022-02-20 16:30:14 +08:00
Wisp X
b507d71ddd 图片列表接口 2022-02-20 16:13:42 +08:00
Wisp X
c047b0a27e 清空 token 接口 2022-02-20 15:17:07 +08:00
Wisp X
c18d2d3169 token 生成接口 2022-02-20 15:08:05 +08:00
Wisp X
b6429aa7cf 接口请求限流 2022-02-20 14:27:23 +08:00
Wisp X
4342c47189 🐛 修复 BUG 2022-02-20 14:10:39 +08:00
Wisp X
33884c3fd1 上传图片接口 2022-02-20 12:54:39 +08:00
Wisp X
b9b3bde750 Introducing new features. 2022-02-20 10:46:37 +08:00
Wisp X
42e3ab4865 📦 Updating compiled files or packages. 2022-02-18 18:51:11 +08:00
Wisp X
e15026749c 完成图片管理功能 2022-02-18 18:49:41 +08:00
Wisp X
1510389709 改进用户管理 2022-02-18 13:58:27 +08:00
Wisp X
e27e539aa3 改进 & 修复 bug 2022-02-18 13:49:31 +08:00
Wisp X
5bbf2ef8d4 图片管理 2022-02-18 13:38:50 +08:00
Wisp X
da0da0ab09 🐛 修复 BUG 2022-02-18 12:09:08 +08:00
Wisp X
36397497a3 管理图片删除 2022-02-18 11:47:54 +08:00
Wisp X
28dc5b0e2e 💄 Updating the UI and style files. 2022-02-17 16:45:58 +08:00
Wisp X
c320c556bc 💄 Updating the UI and style files. 2022-02-17 16:25:42 +08:00
Wisp X
7bb19b6329 改进 2022-02-17 11:49:47 +08:00
Wisp X
0b75785b5e 🐛 修复 BUG 2022-02-17 11:11:07 +08:00
Wisp X
93f7e111ba 🐛 修复 BUG 2022-02-16 15:52:00 +08:00
Wisp X
4c95073963 📦 Updating compiled files or packages. 2022-02-16 15:11:32 +08:00
Wisp X
8c80e70696 增加测试邮件 2022-02-14 14:35:04 +08:00
Wisp X
0e996b579d 完善配置 2022-02-14 14:04:19 +08:00
Wisp X
11857bbe9d 🔧 Changing configuration files. 2022-02-14 11:36:19 +08:00
Wisp X
39238a61d7 🐛 修复 BUG 2022-02-13 17:50:34 +08:00
Wisp X
97d186c5d6 系统设置功能 2022-02-13 17:40:59 +08:00
Wisp X
6305d5b08c 系统设置页 2022-02-13 13:04:07 +08:00
Wisp X
cada455745 仪表盘 2022-02-13 01:13:27 +08:00
Wisp X
90239f9827 🐛 修复 BUG 2022-02-12 22:07:19 +08:00
Wisp X
a7c0620b3e 画廊页面 2022-02-12 19:53:24 +08:00
Wisp X
07e63ccba6 🐛 Fixing a bug. 2022-02-12 19:50:28 +08:00
Wisp X
09d5945e2e 画廊页面 2022-02-11 16:07:08 +08:00
Wisp X
c9fad14bf7 改进画廊样式 2022-02-11 16:05:57 +08:00
Wisp X
57f51e4e43 画廊功能 2022-02-11 15:50:48 +08:00
Wisp X
588f05dfde 画廊页面 2022-02-11 13:48:22 +08:00
Wisp X
6d8031b0a2 🐛 修复 BUG 2022-02-11 08:42:28 +08:00
Wisp X
5d64068456 💬 update readme 2022-02-10 17:18:42 +08:00
Wisp X
593c3bc18a 改进全屏逻辑 2022-02-10 17:03:36 +08:00
Wisp X
dddad809a0 画廊页面 2022-02-10 16:23:05 +08:00
Wisp X
0182e2f350 用户管理 2022-02-10 15:43:02 +08:00
Wisp X
ba2cff0977 🎨 改进 2022-02-10 14:58:53 +08:00
Wisp X
d090983954 🐛 Fixing a bug. 2022-02-10 14:46:35 +08:00
Wisp X
75bf3ec2f7 用户列表 2022-02-10 14:25:45 +08:00
Wisp X
0d4e9514a1 💄 改进样式 2022-02-10 13:10:31 +08:00
Wisp X
63741bd8c5 💄 改进样式 2022-02-10 13:07:36 +08:00
Wisp X
b776938146 💄 Updating the UI and style files. 2022-02-10 11:34:20 +08:00
Wisp X
ec28485e69 📦 Updating compiled files or packages. 2022-02-10 11:10:33 +08:00
Wisp X
ff81bcc5d8 🔧 Changing configuration files. 2022-02-10 11:01:37 +08:00
Wisp X
2b44ab2baa 💬 up readme.md 2022-02-09 15:54:40 +08:00
Wisp X
61c876961c 📦 升级至 laravel 9 正式版 2022-02-09 15:49:36 +08:00
Wisp X
ec5c149a8a 🐛 修复 BUG 2022-02-08 17:47:18 +08:00
Wisp X
267cd948bf 🐛 修复 BUG 2022-02-08 15:49:22 +08:00
Wisp X
175e3fe56a 💄 更新表单配色 2022-02-08 15:11:19 +08:00
Wisp X
05e5376673 保存策略时创建符号链接 2022-01-29 21:39:11 +08:00
Wisp X
0286721429 🐛 修复 BUG 2022-01-27 17:49:16 +08:00
Wisp X
654709b00f 改进结构 2022-01-27 17:43:44 +08:00
Wisp X
b4194c5bc3 🐛 修复 BUG 2022-01-27 17:18:15 +08:00
Wisp X
6f648b2e2f Introducing new features. 2022-01-27 17:11:36 +08:00
Wisp X
2cab28a229 Introducing new features. 2022-01-27 16:58:31 +08:00
Wisp X
8fd4ae7bc8 本地策略符号链接功配置 2022-01-27 15:03:23 +08:00
Wisp X
de43341a31 储存策略更新 2022-01-26 17:51:58 +08:00
Wisp X
d31d1bb8d9 储存策略更新 2022-01-26 17:48:04 +08:00
Wisp X
e42ac5de3b 储存策略添加 2022-01-26 16:40:43 +08:00
Wisp X
d3172f1258 💄 Updating the UI and style files. 2022-01-25 08:47:37 +08:00
Wisp X
3804594829 🐛 修复 BUG 2022-01-25 08:38:28 +08:00
Wisp X
79b7585e35 组件化输入框 2022-01-24 17:58:44 +08:00
Wisp X
a93424be4a 组件化输入框 2022-01-24 17:31:42 +08:00
Wisp X
746eaa8a92 策略创建表单 2022-01-24 17:00:05 +08:00
Wisp X
035e1d140b 改进组设置 2022-01-24 15:58:42 +08:00
Wisp X
ed91764fd5 改进组设置 2022-01-24 15:55:06 +08:00
Wisp X
2ea34bcb77 策略列表 2022-01-24 14:49:15 +08:00
Wisp X
2ee532b313 角色组增删改查 2022-01-23 15:48:45 +08:00
Wisp X
fbd0a59938 🐛 修复 BUG 2022-01-23 15:32:27 +08:00
Wisp X
7b57627044 🐛 修复 BUG 2022-01-23 14:20:40 +08:00
Wisp X
4975f3e1c1 完善角色组编辑页面 2022-01-22 14:19:12 +08:00
Wisp X
5662f86aae 角色组编辑页面 2022-01-22 13:16:15 +08:00
Wisp X
3ac70a772c 🐛 修复 BUG 2022-01-21 23:48:27 +08:00
Wisp X
114b6c4b42 🐛 修复 BUG 2022-01-21 23:38:02 +08:00
Wisp X
c79123b178 🔧 改进配置方式 2022-01-21 23:31:38 +08:00
Wisp X
9dadc84f21 💄 角色组页面创建样式 2022-01-21 22:47:06 +08:00
Wisp X
95fd3fc930 改进验证器 2022-01-21 15:47:18 +08:00
Wisp X
ffc2895a6c 完善角色组创建页面样式 2022-01-21 14:36:17 +08:00
Wisp X
84e9caeb71 💄 改进角色组创建页面样式 2022-01-21 11:33:09 +08:00
Wisp X
3134fc437d 🔧 Changing configuration files. 2022-01-21 09:01:15 +08:00
Wisp X
54efefbe39 💄 Updating the UI and style files. 2022-01-21 08:01:55 +08:00
Wisp X
13ac6b064d 💄 Updating the UI and style files. 2022-01-21 07:58:02 +08:00
Wisp X
5679d3876d 💄 角色组创建表单页面样式 2022-01-20 22:42:44 +08:00
Wisp X
90863c8d1c 组于策略改为多对多关系 2022-01-20 20:41:55 +08:00
Wisp X
4880935844 🐛 修复 BUG 2022-01-20 17:14:24 +08:00
Wisp X
031c0f76ee 💄 Updating the UI and style files. 2022-01-20 17:13:43 +08:00
Wisp X
0b4fd797a3 角色组列表 2022-01-20 16:48:49 +08:00
Wisp X
3507034427 🐛 修复 BUG 2022-01-20 13:11:36 +08:00
Wisp X
74d89ee9a6 管理员中间件 2022-01-20 09:20:33 +08:00
Wisp X
ef4d214a9e 🐛 修复 BUG 2022-01-20 08:04:10 +08:00
Wisp X
95fd2298e7 🐛 修复 BUG 2022-01-19 22:59:20 +08:00
Wisp X
78b87d60f0 应用上传后自动清除预览图功能 2022-01-19 22:49:45 +08:00
Wisp X
89dd7b75c7 应用用户设置的默认图片权限 2022-01-19 22:44:43 +08:00
Wisp X
615da555a5 用户设置功能 2022-01-19 22:40:25 +08:00
Wisp X
39feb32ba9 📦 Updating compiled files or packages. 2022-01-19 20:28:12 +08:00
Wisp X
d6564bbfbd 🎨 改进图片列表 2022-01-19 20:17:46 +08:00
Wisp X
cd71b194e6 改进缩略图生成算法 2022-01-19 15:05:44 +08:00
Wisp X
4e38a53971 🐛 修复动图上传后没有动画的bug 2022-01-19 13:37:34 +08:00
Wisp X
cf745ec73a 图片增加缓存 2022-01-19 13:33:10 +08:00
Wisp X
046d642a87 水印跳过 gif 动图 2022-01-19 13:09:24 +08:00
Wisp X
d56e605e35 改进试图 2022-01-19 11:37:33 +08:00
Wisp X
24a0a576e8 更换图片处理库 2022-01-19 11:00:25 +08:00
Wisp X
d8ee38573b 🐛 修复 BUG 2022-01-19 09:38:38 +08:00
Wisp X
0e8ac365a5 改进水印功能 2022-01-19 08:51:15 +08:00
Wisp X
e1c6201c7c 🐛 修复 BUG 2022-01-19 08:36:08 +08:00
Wisp X
6a0704b183 图片水印支持 2022-01-18 22:57:34 +08:00
Wisp X
526e32a5f0 增加平铺水印支持 2022-01-18 21:54:55 +08:00
Wisp X
83bee48279 文字水印支持 2022-01-18 14:16:40 +08:00
Wisp X
5d87c5dbbc 改进缩略图生成算法 2022-01-18 11:07:32 +08:00
Wisp X
34af734d01 🐛 修复 BUG 2022-01-17 22:52:40 +08:00
Wisp X
82ec6c6ac6 改进组配置,修复bug 2022-01-17 22:45:18 +08:00
Wisp X
9718871610 支持复制缩略图链接 2022-01-17 16:19:53 +08:00
Wisp X
ff2162cbe9 改进缩略图生成质量 2022-01-17 14:19:25 +08:00
Wisp X
3298e3f4d8 移除缓存增加标记 2022-01-17 13:56:50 +08:00
Wisp X
7e142dd5c6 图片缓存增加标记 2022-01-17 13:53:36 +08:00
Wisp X
7aa085d19f 原图保护功能 2022-01-17 13:49:20 +08:00
Wisp X
84a4698e97 缩略图增加缓存机制 2022-01-17 13:26:05 +08:00
Wisp X
c919c556ca 🐛 修复 BUG 2022-01-17 11:28:28 +08:00
Wisp X
a6d0b9edff 🐛 修复 BUG 2022-01-17 11:12:28 +08:00
Wisp X
8e8113af50 动态生成缩略图 2022-01-17 11:06:42 +08:00
Wisp X
c9bca05daa 原图保护功能 2022-01-17 09:59:55 +08:00
Wisp X
90eb5c71e9 策略增加原图保护功能 2022-01-17 09:09:31 +08:00
Wisp X
4cbdcf8354 📦 Updating compiled files or packages. 2022-01-17 08:31:42 +08:00
Wisp X
d59e0c7bd1 📦 安装调试工具 2022-01-15 10:49:42 +08:00
Wisp X
6cdf71dcb4 Introducing new features. 2022-01-15 10:07:36 +08:00
Wisp X
a051ccb2c6 Introducing new features. 2022-01-14 17:38:37 +08:00
Wisp X
b1e79f846c 🐛 修复 BUG 2022-01-14 16:41:26 +08:00
Wisp X
08c8ce757e 🐛 修复 BUG 2022-01-14 16:41:03 +08:00
Wisp X
6e2088f5b6 🐛 修复 BUG 2022-01-14 16:39:33 +08:00
Wisp X
6f96b298a3 🐛 修复 BUG 2022-01-14 16:38:07 +08:00
Wisp X
b38957d374 动态更新容量进度条 2022-01-14 16:26:07 +08:00
Wisp X
8e4d9f23ad 🐛 修复 BUG 2022-01-14 15:24:48 +08:00
Wisp X
8d04e56572 🐛 修复 BUG 2022-01-14 15:20:16 +08:00
Wisp X
3a1095dcb3 邮件验证功能 2022-01-14 14:28:58 +08:00
Wisp X
f12beb3fd2 🐛 修复 BUG 2022-01-14 13:17:54 +08:00
Wisp X
f3bc257a87 🐛 修复 BUG 2022-01-14 10:55:08 +08:00
Wisp X
0cba2cea6c 支持查看图片详细信息 2022-01-14 10:49:13 +08:00
Wisp X
ee1a984abf 🐛 修复 BUG 2022-01-13 17:07:09 +08:00
Wisp X
7db4615272 完善我的图片管理 2022-01-13 16:46:15 +08:00
Wisp X
fbd7470cd6 💄 移动端构建菜单 2022-01-13 15:50:51 +08:00
Wisp X
19b76634e6 💄 移动端构建菜单 2022-01-13 15:40:51 +08:00
Wisp X
5dff05f791 🐛 修复 BUG 2022-01-13 11:37:46 +08:00
Wisp X
81016e5643 图片设置权限功能 2022-01-13 11:21:21 +08:00
Wisp X
3e911db0ca 图片重命名功能 2022-01-13 10:49:56 +08:00
Wisp X
f36ba0db6b 图片重命名功能 2022-01-13 10:45:26 +08:00
Wisp X
0481188a66 🐛 修复搜索无法输入内容的BUG 2022-01-13 08:45:10 +08:00
Wisp X
839b1b5927 权限筛选 2022-01-13 08:40:56 +08:00
Wisp X
44b851d86a 📦 更新包版本 2022-01-13 08:20:07 +08:00
Wisp X
eb4c7117a1 🎨 改进模型 2022-01-12 13:49:33 +08:00
Wisp X
6464851d2e 迁移至 laravel 9 2022-01-12 13:26:39 +08:00
Wisp X
fba806d24a 更改图片服务类名 2022-01-12 08:49:46 +08:00
Wisp X
c5cf0081ae 图片删除功能 2022-01-12 08:47:21 +08:00
Wisp X
47a36f7c26 💄 Updating the UI and style files. 2022-01-06 17:59:40 +08:00
Wisp X
edb5c2a6b4 删除相册功能 2022-01-05 08:56:21 +08:00
Wisp X
a00cdb4f7f 改进 contextjs 组件 2021-12-31 17:13:15 +08:00
Wisp X
f8f9422491 改进 contextjs 组件 2021-12-31 15:06:07 +08:00
Wisp X
da077dc8bf 改进右键操作逻辑 2021-12-31 14:08:22 +08:00
Wisp X
4d96077769 💄 Updating the UI and style files. 2021-12-31 13:36:15 +08:00
Wisp X
e0b68d5470 🐛 修复 BUG 2021-12-31 10:47:10 +08:00
Wisp X
844d768fd3 完善移出相册功能 2021-12-31 09:27:10 +08:00
Wisp X
7117198e59 改进 contextjs,完善移出相册功能 2021-12-31 09:20:05 +08:00
Wisp X
10f38ad57d 图片移动到相册功能 2021-12-30 16:51:03 +08:00
Wisp X
e4823e262a Introducing new features. 2021-12-30 16:02:39 +08:00
Wisp X
f757a882d9 🐛 修复 BUG 2021-12-30 15:13:51 +08:00
Wisp X
4897e6e6c8 复制图片功能 2021-12-30 15:09:55 +08:00
Wisp X
d609d5527c 复制图片功能 2021-12-30 11:39:46 +08:00
Wisp X
cdd7269ff4 复制图片功能 2021-12-30 11:39:26 +08:00
Wisp X
facbc1269b 改进 context-js 组件 2021-12-29 17:38:58 +08:00
Wisp X
935f81c03f 完善右键菜单选项 2021-12-28 22:05:59 +08:00
Wisp X
0ecf67188a 🐛 修复 BUG 2021-12-28 20:05:09 +08:00
Wisp X
5bdd05960e Introducing new features. 2021-12-28 20:01:49 +08:00
Wisp X
62402ef8b4 🐛 Fixing a bug 2021-12-28 10:15:55 +08:00
Wisp X
91a5f606df 🐛 Fixing a bug 2021-12-27 22:36:54 +08:00
Wisp X
8b584b54e4 相册编辑功能 2021-12-26 14:26:12 +08:00
Wisp X
6a4a57aa65 ajax替换为axios 2021-12-26 13:56:02 +08:00
Wisp X
b25272346c 文件夹创建功能 2021-12-26 13:12:08 +08:00
Wisp X
fe52158d8e 文件夹创建功能 2021-12-26 12:16:36 +08:00
Wisp X
173cf43619 🐛 修复 BUG 2021-12-25 23:32:46 +08:00
Wisp X
013a71234b 完善相册样式 2021-12-25 23:29:53 +08:00
Wisp X
f326f74b76 改进鼠标选框 2021-12-25 09:18:31 +08:00
Wisp X
5cd405ea7c 我的图片鼠标选框 2021-12-25 01:38:58 +08:00
Wisp X
50d79fc2c2 完成相册筛选 2021-12-24 15:52:18 +08:00
Wisp X
7c5e52591f 🐛 修复 BUG 2021-12-24 14:43:07 +08:00
Wisp X
3b6971d648 🐛 修复 BUG 2021-12-24 09:15:04 +08:00
Wisp X
fdb55f1c2b 🎨 改进 ajax 拦截器 2021-12-24 08:51:01 +08:00
Wisp X
15685d664f 我的图片支持关键字搜索 2021-12-23 22:49:40 +08:00
Wisp X
bb78e935a8 我的图片支持排序 2021-12-23 22:23:30 +08:00
Wisp X
f4a1064b15 我的图片支持排序 2021-12-23 22:13:10 +08:00
Wisp X
09df3deeea 封装上拉加载组件 2021-12-23 21:44:25 +08:00
Wisp X
55ece284da 💄 Updating the UI and style files. 2021-12-23 08:54:45 +08:00
Wisp X
b67ae153f9 💄 我的图片页面抽屉组件 2021-12-22 14:25:30 +08:00
Wisp X
aca4acbc8e 💄 改进样式 2021-12-22 12:33:23 +08:00
Wisp X
dde30bde77 💄 Updating the UI and style files. 2021-12-22 08:27:11 +08:00
Wisp X
035c184761 💄 完善我的图片页面选择样式 2021-12-21 23:16:31 +08:00
Wisp X
44d096c8ed 上传图片时储存图片宽高 2021-12-21 09:09:54 +08:00
Wisp X
708d3e5137 🐛 修复 BUG 2021-12-20 22:59:08 +08:00
Wisp X
f3b5840c3e Revert "💄 Updating the UI and style files."
This reverts commit 6c61ccc3e4.
2021-12-20 22:49:03 +08:00
Wisp X
e158be2ce2 Introducing new features. 2021-12-20 22:07:00 +08:00
Wisp X
9842338d25 其他改进 2021-12-20 22:05:35 +08:00
Wisp X
cbe0f58f2e 取消自动更新检测 2021-12-20 22:01:55 +08:00
Wisp X
10073b8f98 取消自动更新检测 2021-12-20 22:00:03 +08:00
Wisp X
c2ae231293 📦 更新 phpmailer/phpmailer 2021-12-20 21:56:19 +08:00
Wisp X
64e328f6b3 增加一键复制功能 Closed #167 2021-12-20 21:54:41 +08:00
Wisp X
33af412423 更新 api 文档 2021-12-20 21:32:38 +08:00
Wisp X
fc385d49a8 显示用户的注册时间 2021-12-20 21:29:15 +08:00
Wisp X
08982260fb 改进登录方式 2021-12-20 21:27:25 +08:00
Wisp X
4689af7768 📦 将第三方资源放置本地 2021-12-20 21:23:21 +08:00
WispX
65baffe44e 📦 将第三方资源放置本地 2021-12-20 18:58:54 +08:00
WispX
16ac2b8144 📦 将第三方资源放置本地 2021-12-20 18:33:23 +08:00
WispX
8ad22cc147 📦 将第三方资源放置本地 2021-12-20 18:23:05 +08:00
WispX
6c61ccc3e4 💄 Updating the UI and style files. 2021-12-20 11:13:22 +08:00
WispX
3289b9d688 💄 Updating the UI and style files. 2021-12-20 08:56:20 +08:00
WispX
4518bfdbce 💄 完善我的图片页面 2021-12-19 22:31:14 +08:00
WispX
01b4502c45 💄 完成我的图片页面基础样式 2021-12-19 16:06:48 +08:00
WispX
68a55a8617 💄 Updating the UI and style files. 2021-12-18 21:08:43 +08:00
WispX
4a9715d77f 仪表盘数据展示 2021-12-18 16:12:58 +08:00
WispX
d229ef18a8 永久缓存系统配置 2021-12-17 23:39:22 +08:00
WispX
cdd40511ea 完善文件命名规则方法 2021-12-17 23:29:50 +08:00
WispX
2019fb86b7 🐛 修复 BUG 2021-12-17 16:09:48 +08:00
WispX
9ee8d51852 🐛 修复 BUG 2021-12-17 16:02:36 +08:00
WispX
bbdad3f911 改进上传服务 2021-12-17 15:57:01 +08:00
WispX
7505616e3f 改进上传服务 2021-12-17 15:55:30 +08:00
WispX
d795171461 完善文件上传服务 2021-12-17 14:50:41 +08:00
WispX
f055126a69 完善文件上传服务 2021-12-17 14:50:09 +08:00
WispX
970205e56d 完善文件上传服务 2021-12-17 14:35:35 +08:00
WispX
010c33f18c 完善文件上传服务 2021-12-17 14:28:16 +08:00
WispX
3f6df9d2a0 文件上传功能 2021-12-17 11:37:34 +08:00
WispX
1da4a536ba 完善策略配置 2021-12-16 22:47:00 +08:00
WispX
9bd446e822 获取配置方法,缓存 1 天 2021-12-16 11:26:09 +08:00
WispX
5efd4f5b71 获取配置方法,缓存 1 天 2021-12-16 11:02:33 +08:00
WispX
5d7e5a3cad 初始化配置 seed 2021-12-16 10:25:42 +08:00
WispX
06fa1e5636 💄 配置常量 2021-12-15 22:44:49 +08:00
WispX
ac1b39070a 💄 完善上传组件 2021-12-15 20:41:58 +08:00
WispX
8040d0de1f 💄 完善上传组件 2021-12-15 19:39:14 +08:00
WispX
3282c62b22 💄 替换上传组件 2021-12-14 23:20:52 +08:00
WispX
f91c4b2fbf 💄 Updating the UI and style files. 2021-12-14 11:02:15 +08:00
WispX
e2e0a2ce9a Introducing new features. 2021-12-13 22:46:05 +08:00
WispX
edc61f42d9 💄 Updating the UI and style files. 2021-12-13 21:35:43 +08:00
WispX
80e8682a5f 🗃️ 图片表增加访问权限字段 2021-12-13 21:27:16 +08:00
WispX
c5de08c00b 完善未登录状态首页样式 2021-12-13 20:56:06 +08:00
WispX
7fded3473a 🐛 Fixing a bug. 2021-12-13 19:55:39 +08:00
WispX
b7788d0e5e 🔧 Changing configuration files. 2021-12-13 10:22:39 +08:00
WispX
bdae76cadb 💄 复制全部链接功能 Closed #167 2021-12-13 10:11:07 +08:00
WispX
2cca08a2cd 💄 完善上传组件 2021-12-13 09:45:02 +08:00
WispX
fd863c8a4a 💄 完善上传组件 2021-12-13 09:23:31 +08:00
WispX
dc8189335f 💄 完善上传组件 2021-12-12 21:54:48 +08:00
WispX
73742f886e 💄 完善上传组件 2021-12-12 20:57:10 +08:00
WispX
617273a9a4 💄 改进样式 2021-12-12 15:40:42 +08:00
WispX
8f03aebcdc 💄 改进样式 2021-12-12 15:37:34 +08:00
WispX
4466003203 上传组件基础样式 2021-12-12 15:23:08 +08:00
WispX
dc91c49713 完善模型 2021-12-11 21:50:32 +08:00
WispX
8853cac38d 重新设计表结构 2021-12-11 20:06:04 +08:00
WispX
874a554829 页面基本布局、登录、注册、找回密码 2021-12-11 17:24:44 +08:00
WispX
9800d5df07 🎉 install laravel framework. 2021-12-10 09:11:15 +08:00
WispX
82988ebe2e 🔧 Changing configuration files. 2021-09-23 11:26:42 +08:00
Redy
39b1ac1705 用户管理和用户设置前台显示创建时间 (#263) 2021-09-23 11:19:17 +08:00
WispX
e7a08e5932 🐛 修复 BUG 2021-07-30 17:09:35 +08:00
WispX
b8de26f515 🐛 修复 BUG 2021-07-28 10:59:34 +08:00
WispX
11327ed215 🐛 修复 BUG 2021-07-28 10:35:59 +08:00
WispX
fbbc69f80a 🎨 改进登录方式 2021-07-28 10:27:22 +08:00
WispX
e4e7e57580 🐛 修复 BUG 2021-07-28 10:21:39 +08:00
WispX
c5bed1e7a4 🎨 Improve performance 2021-07-28 10:14:48 +08:00
WispX
83d794acb4 🎨 Improve performance 2021-07-28 10:02:34 +08:00
WispX
635d05e217 国际化 2021-07-27 10:53:27 +08:00
WispX
8e3179ba42 Update bug_report.md 2021-07-27 09:41:56 +08:00
WispX
5f7d056a77 Merge branch 'master' of https://github.com/wisp-x/lsky-pro into dev 2021-07-27 09:41:03 +08:00
Wisp X
5147586776 Update bug_report.md 2021-07-27 09:39:55 +08:00
Wisp X
2a00978441 Update issue templates 2021-07-27 09:36:25 +08:00
WispX
b8a8174d0c 多语言开关 2021-07-27 09:24:40 +08:00
WispX
89f537c4e2 多语言开关 2021-07-27 09:24:20 +08:00
WispX
d09d57dbe1 Add Multilingualism. Closed #145 2021-07-26 17:58:17 +08:00
WispX
c09bece154 🐛 Fixed a bug that failed to send a password recovery email 2021-07-26 14:55:56 +08:00
WispX
0c0b60f86c 🐛 修复 BUG 2021-07-26 14:18:18 +08:00
WispX
77cbabf20b 🐛 修复 BUG 2021-07-26 13:38:15 +08:00
WispX
f951a2bd06 模板多语言 2021-07-26 13:25:43 +08:00
WispX
7b5634b439 模板多语言 2021-07-26 13:23:26 +08:00
WispX
b704a2c30a Adaptive Multilingualism. 2021-07-23 17:46:26 +08:00
WispX
fbea72c239 📦 Updating compiled files or packages. 2021-07-22 09:41:02 +08:00
WispX
5565b02991 Create FUNDING.yml 2021-07-21 16:57:20 +08:00
WispX
876e45b55c Merge branch 'master' into dev 2021-07-21 16:56:57 +08:00
WispX
8afd8b4f21 Create FUNDING.yml 2021-07-21 16:56:40 +08:00
WispX
6377815540 Create FUNDING.yml 2021-07-21 16:56:10 +08:00
WispX
b5ae9bbe90 Create FUNDING.yml 2021-07-21 16:55:10 +08:00
WispX
4708057b8b Merge branch 'master' of https://github.com/wisp-x/lsky-pro into dev 2021-07-21 16:48:21 +08:00
Wisp X
edcadf1c3c Create FUNDING.yml 2021-07-21 16:45:02 +08:00
WispX
5c3d5b7ec4 🐛 Fixed The undefined folder. Fixed #240 2021-07-21 13:39:15 +08:00
WispX
6226f4b445 🐛 Fixed picture management page turning bug. Closed #236 2021-07-19 17:04:41 +08:00
Wisp X
0688d1192c ⬆️ Change version 2021-07-17 22:00:40 +08:00
Licoy
f1d30f03c1 增加相同ip一天内最大上传限制功能 2021-07-17 22:00:40 +08:00
dependabot[bot]
f53fed14f7 ⬆️ Bump phpmailer/phpmailer from 6.4.1 to 6.5.0
Bumps [phpmailer/phpmailer](https://github.com/PHPMailer/PHPMailer) from 6.4.1 to 6.5.0.
- [Release notes](https://github.com/PHPMailer/PHPMailer/releases)
- [Changelog](https://github.com/PHPMailer/PHPMailer/blob/master/changelog.md)
- [Commits](https://github.com/PHPMailer/PHPMailer/compare/v6.4.1...v6.5.0)

---
updated-dependencies:
- dependency-name: phpmailer/phpmailer
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-06-24 08:26:36 +08:00
WispX
e5fce6f1dd 💬 update readme.md 2021-06-09 13:06:34 +08:00
WispX
28e8f8dc2a 💄 更改鉴黄内容评级的选项描述 2021-05-19 08:31:01 +08:00
WispX
af52759a1b Merge branch 'master' into dev 2021-05-18 13:44:06 +08:00
WispX
d51c4e6d3e 🐛 修复 BUG 2021-05-18 13:43:54 +08:00
WispX
774cba8658 Merge branch 'master' into dev 2021-05-18 13:42:37 +08:00
dependabot[bot]
4c789dd99a ⬆️ Bump phpmailer/phpmailer from 6.4.0 to 6.4.1
Bumps [phpmailer/phpmailer](https://github.com/PHPMailer/PHPMailer) from 6.4.0 to 6.4.1.
- [Release notes](https://github.com/PHPMailer/PHPMailer/releases)
- [Changelog](https://github.com/PHPMailer/PHPMailer/blob/master/changelog.md)
- [Commits](https://github.com/PHPMailer/PHPMailer/compare/v6.4.0...v6.4.1)

Signed-off-by: dependabot[bot] <support@github.com>
2021-05-05 15:13:16 +08:00
WispX
bde8217a6d 🐛 修复 BUG 2021-04-22 16:00:30 +08:00
WispX
de90d79fbd 🐛 修复 BUG 2021-04-21 17:35:17 +08:00
WispX
089f4fdde8 💬 Updating readme.md. 2021-04-21 16:38:44 +08:00
WispX
e985da4492 Introducing new features. 2021-04-21 16:33:55 +08:00
WispX
1fc8835018 💬 Updating readme.md. 2021-04-21 16:14:58 +08:00
WispX
2ed547a735 Added automatic watermark features. Closes (#196, #186, #138, #85, #75) 2021-04-21 16:12:55 +08:00
WispX
5e4d4a7161 The token is shown in the materials. Closed #157 2021-04-21 09:51:49 +08:00
WispX
d9065778aa The token is shown in the materials. Closed 157 2021-04-21 09:50:38 +08:00
WispX
749583a11e 💄 Updating the UI and style files. 2021-04-21 09:07:08 +08:00
WispX
9a54fcaa52 💄 Updating the UI and style files. 2021-04-21 09:04:03 +08:00
WispX
271981d0f0 💄 Updating the UI and style files. 2021-04-21 08:59:01 +08:00
WispX
5231f8d541 Added gallery display feature. 2021-04-20 10:35:33 +08:00
WispX
703d686d5e 🐛 Fixed the bug of background IP interface failure. Closed #179 2021-04-19 15:24:14 +08:00
WispX
989f0d323a 💄 Modify the link of public security record system. 2021-04-16 17:39:36 +08:00
WispX
1bbd06cb28 Upload return image id. Closed #180 2021-04-16 17:29:10 +08:00
WispX
26840ede07 📦 Compatible with PHP8. Closed #192 2021-04-16 16:57:16 +08:00
WispX
1a4a317faf 🐛 Fixed image paste naming problem. Closed #194 2021-04-16 16:52:36 +08:00
WispX
d30a29e89e 🐛 Fixing a bug. 2021-01-21 11:04:51 +08:00
WispX
b23fc23788 上传接口返回ID 2020-11-20 14:15:43 +08:00
WispX
97b2eafdae 原始文件名命名规则支持与其他规则组合 2020-08-11 15:52:07 +08:00
WispX
a4bbadaa88 文件路径命名规则可为空。Closed #159 2020-08-11 15:24:50 +08:00
WispX
931c546257 文件路径命名规则可为空。Closed #159 2020-08-11 14:49:03 +08:00
WispX
32aafe5f1b 🐛 Fixing a bug. 2020-08-11 14:34:35 +08:00
WispX
a828a16890 🐛 Fixing a bug. Closed #156 2020-08-11 14:28:06 +08:00
WispX
02adeab97f 🐛 Fixing a bug. Closed #156 2020-08-11 14:15:20 +08:00
WispX
676a1efa7e 💬 UPDATE README.MD 2020-08-11 14:12:01 +08:00
WispX
7721e49a78 🐛 Fixing a bug. 2020-07-15 11:39:21 +08:00
WispX
678ba4f73d 更改后台图片管理每页显示数量为 25 条 2020-07-15 11:36:16 +08:00
WispX
d68c671d48 📌 Pinning dependencies to specific versions. 2020-07-15 11:27:53 +08:00
WispX
8093181585 💄 Updating the UI and style files. 2020-07-15 11:05:18 +08:00
WispX
a12ef5fd60 取消 coding 托管仓库。 2020-07-15 10:41:23 +08:00
WispX
9bdad2aeae 📦 Updating compiled files or packages. 2020-07-15 10:38:33 +08:00
WispX
db995622a9 增加八位数随机字符串命名规则. Closed #121 2020-06-11 17:59:38 +08:00
WispX
d39e40a4ba 🐛 修复后台图片列表切换 select 后点击下一页数据被重置的 bug. Closed #128 2020-06-11 17:56:15 +08:00
WispX
e88ef59b7b Merge branches 'dev' and 'master' of https://github.com/wisp-x/lsky-pro into dev 2020-06-11 17:04:13 +08:00
dependabot[bot]
bfe9e322aa ⬆️ Bump phpmailer/phpmailer from 6.1.4 to 6.1.6
Bumps [phpmailer/phpmailer](https://github.com/PHPMailer/PHPMailer) from 6.1.4 to 6.1.6.
- [Release notes](https://github.com/PHPMailer/PHPMailer/releases)
- [Changelog](https://github.com/PHPMailer/PHPMailer/blob/master/changelog.md)
- [Commits](https://github.com/PHPMailer/PHPMailer/compare/v6.1.4...v6.1.6)

Signed-off-by: dependabot[bot] <support@github.com>
2020-05-29 06:55:46 +08:00
WispX
2ffb2dc8b8 💄 Updating the UI and style files. 2020-04-22 08:39:55 +08:00
WispX
9e6f1d4025 Introducing new features. Closed #73 2020-04-21 22:03:22 +08:00
WispX
f35dfcea36 🐛 修复用户图片列表中图片分页后无法搜索到指定图片的 bug. Closed #99 2020-04-21 21:09:14 +08:00
WispX
b14e790fbb 🐛 修复自定义链接参数后上传返回的链接中出现重复参数的 bug. Closed #118 2020-04-21 21:04:59 +08:00
WispX
d9c6cf4b5c 🐛 Fixing a bug. Closed #112 2020-04-21 18:00:13 +08:00
WispX
6ba708143d 🐛 Fixing a bug. 2020-03-19 22:22:39 +08:00
WispX
eac3cade5c 改进用户注册验证条件 2020-03-19 16:48:32 +08:00
WispX
dc78ef18af 💬 Updating text and literals. 2020-03-19 16:36:15 +08:00
1732 changed files with 70750 additions and 195757 deletions

View File

@@ -8,8 +8,11 @@ indent_style = space
indent_size = 4
trim_trailing_whitespace = true
[*.{html, less, js}]
indent_size = 2
[*.md]
trim_trailing_whitespace = false
[*.{yml,yaml}]
indent_size = 2
[docker-compose.yml]
indent_size = 4

View File

@@ -1,17 +1,39 @@
[app]
debug = false
trace = false
APP_NAME="Lsky Pro"
APP_ENV=prod
APP_KEY=
APP_DEBUG=false
APP_URL=http://localhost
[database]
hostname = {hostname}
database = {database}
username = {username}
password = {password}
hostport = {hostport}
charset = utf8mb4
prefix = lsky_
LOG_CHANNEL=daily
LOG_DEPRECATIONS_CHANNEL=null
LOG_LEVEL=debug
[system]
single_user_mode = false
intercept_salacity = false
url_query = ''
DB_CONNECTION=
DB_HOST=
DB_PORT=
DB_DATABASE=
DB_USERNAME=
DB_PASSWORD=
BROADCAST_DRIVER=log
CACHE_DRIVER=file
FILESYSTEM_DISK=public
QUEUE_CONNECTION=sync
SESSION_DRIVER=file
SESSION_LIFETIME=120
MEMCACHED_HOST=127.0.0.1
REDIS_HOST=127.0.0.1
REDIS_PASSWORD=null
REDIS_PORT=6379
PUSHER_APP_ID=
PUSHER_APP_KEY=
PUSHER_APP_SECRET=
PUSHER_APP_CLUSTER=mt1
MIX_PUSHER_APP_KEY="${PUSHER_APP_KEY}"
MIX_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}"
IGNITION_SHARING_ENABLED=false

10
.gitattributes vendored Normal file
View File

@@ -0,0 +1,10 @@
* text=auto
*.blade.php diff=html
*.css diff=css
*.html diff=html
*.md diff=markdown
*.php diff=php
/.github export-ignore
CHANGELOG.md export-ignore

1
.github/FUNDING.yml vendored Normal file
View File

@@ -0,0 +1 @@
custom: https://github.com/wisp-x/lsky-pro#-%E6%8D%90%E8%B5%A0

35
.github/workflows/main.yml vendored Normal file
View File

@@ -0,0 +1,35 @@
name: Lsky-pro
on:
push:
branches: [ dev ]
pull_request:
branches: [ dev ]
jobs:
lsky-pro-tests:
runs-on: ubuntu-latest
steps:
- uses: shivammathur/setup-php@15c43e89cdef867065b0213be354c2841860869e
with:
php-version: '8.0'
- uses: actions/checkout@v2
- name: Copy .env
run: php -r "file_exists('.env') || copy('.env.example', '.env');"
- name: Install Dependencies
run: composer install -q --no-ansi --no-interaction --no-scripts --no-progress --prefer-dist
- name: Generate key
run: php artisan key:generate
- name: Directory Permissions
run: chmod -R 777 storage bootstrap/cache
- name: Create Database
run: |
mkdir -p database
touch database/database.sqlite
- name: Execute tests (Unit and Feature tests) via PHPUnit
env:
DB_CONNECTION: sqlite
DB_DATABASE: database/database.sqlite
run: vendor/bin/phpunit

53
.gitignore vendored
View File

@@ -1,35 +1,20 @@
# General
.DS_Store
.AppleDouble
.LSOverride
.idea
.vscode
*.iml
runtime/.tmp
/public/20*
/application/install.lock
backups
/installed.lock
/upgrading.lock
/*.zip
/node_modules
/public/hot
/public/storage
/public/js/custom.js
/storage/*.key
/vendor
.env
# Icon must end with two \r
Icon
# Thumbnails
._*
# Files that might appear in the root of a volume
.DocumentRevisions-V100
.fseventsd
.Spotlight-V100
.TemporaryItems
.Trashes
.VolumeIcon.icns
.com.apple.timemachine.donotpresent
# Directories potentially created on remote AFP share
.AppleDB
.AppleDesktop
Network Trash Folder
Temporary Items
.apdisk
.env.backup
.phpunit.result.cache
docker-compose.override.yml
Homestead.json
Homestead.yaml
npm-debug.log
yarn-error.log
/.idea
/.vscode
/public/i

13
.styleci.yml Normal file
View File

@@ -0,0 +1,13 @@
php:
preset: laravel
version: 8
disabled:
- no_unused_imports
finder:
not-name:
- index.php
js:
finder:
not-name:
- webpack.mix.js
css: true

View File

@@ -652,7 +652,7 @@ Also add information on how to contact you by electronic and paper mail.
If the program does terminal interaction, make it output a short
notice like this when it starts in an interactive mode:
Lsky Pro Copyright (C) 2018 熊二哈
Lsky Pro Copyright (C) 2018 Wisp X
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.

140
README.md
View File

@@ -1,99 +1,101 @@
<img align="right" width="100" src="./public/static/app/images/icon.png" alt="Lsky Pro Logo"/>
<img align="right" width="100" src="https://avatars.githubusercontent.com/u/100565733?s=200" alt="Lsky Pro Logo"/>
<h1 align="left"><a href="https://www.lsky.pro">Lsky Pro</a></h1>
☁ Your photo album on the cloud.
[![PHP](https://img.shields.io/badge/PHP->=5.6-orange.svg)](http://php.net)
[![Release](https://img.shields.io/github/v/release/wisp-x/lsky-pro)](https://github.com/wisp-x/lsky-pro/releases)
[![Issues](https://img.shields.io/github/issues/wisp-x/lsky-pro)](https://github.com/wisp-x/lsky-pro/issues)
[![Code size](https://img.shields.io/github/languages/code-size/wisp-x/lsky-pro?color=blueviolet)](https://github.com/wisp-x/lsky-pro)
[![Repo size](https://img.shields.io/github/repo-size/wisp-x/lsky-pro?color=eb56fd)](https://github.com/wisp-x/lsky-pro)
[![Languages](https://img.shields.io/github/languages/count/wisp-x/lsky-pro?color=ff6565)](https://github.com/wisp-x/lsky-pro)
[![Last commit](https://img.shields.io/github/last-commit/wisp-x/lsky-pro/dev)](https://github.com/wisp-x/lsky-pro/commits/dev)
[![License](https://img.shields.io/badge/license-GPL_V3.0-yellowgreen.svg)](https://github.com/wisp-x/lsky-pro/blob/master/LICENSE)
[![PHP](https://img.shields.io/badge/PHP->=8.0-orange.svg)](http://php.net)
[![Release](https://img.shields.io/github/v/release/lsky-org/lsky-pro)](https://github.com/lsky-org/lsky-pro/releases)
[![Issues](https://img.shields.io/github/issues/lsky-org/lsky-pro)](https://github.com/lsky-org/lsky-pro/issues)
[![Code size](https://img.shields.io/github/languages/code-size/lsky-org/lsky-pro?color=blueviolet)](https://github.com/lsky-org/lsky-pro)
[![Repo size](https://img.shields.io/github/repo-size/lsky-org/lsky-pro?color=eb56fd)](https://github.com/lsky-org/lsky-pro)
[![Last commit](https://img.shields.io/github/last-commit/lsky-org/lsky-pro/dev)](https://github.com/lsky-org/lsky-pro/commits/dev)
[![License](https://img.shields.io/badge/license-GPL_V3.0-yellowgreen.svg)](https://github.com/lsky-org/lsky-pro/blob/master/LICENSE)
[文档](https://www.kancloud.cn/wispx/lsky-pro) &nbsp;
[演示](https://pic.iqy.ink) &nbsp;
[Chrome 拓展](https://github.com/wisp-x/lsky-pro-chrome-extension) &nbsp;
[官网](https://www.lsky.pro) &middot;
[文档](https://docs.lsky.pro) &middot;
[社区](https://github.com/lsky-org/lsky-pro/discussions) &middot;
[演示](https://pic.vv1234.cn) &middot;
[Telegram 群组](https://t.me/lsky_pro)
> 下载稳定版请点击[这里](https://github.com/wisp-x/lsky-pro/releases),发现 bug 可发送邮件至邮箱i@wispx.cn或提交 [issues](https://github.com/wisp-x/lsky-pro/issues)
> 下载速度慢的可以移步 Coding https://wispx.coding.net/p/lsky-pro-releases/d/lsky-pro-releases/git
> [!WARNING]
> 开源版本已停止维护,不再进行新特性更新和 bug 修复。
![homepage.png](./public/static/app/images/demo/1.png)
![homepage.png](./public/static/app/images/demo/2.png)
> master 分支为未安装三方拓展的版本,通常包含了最新未发布版本的一些实验性新特性和修复补丁,正式版本请点击 [这里](https://github.com/lsky-org/lsky-pro/releases) 下载。
> 发现 bug 请提交 [issues](https://github.com/lsky-org/lsky-pro/issues) (提问前建议阅读[提问的智慧](https://github.com/ryanhanwu/How-To-Ask-Questions-The-Smart-Way/blob/main/README-zh_CN.md))
> 有任何想法、建议、或分享,请移步 [社区](https://github.com/lsky-org/lsky-pro/discussions)
![看不见图片请使用科学上网](https://user-images.githubusercontent.com/22728201/157242302-bfbd04a0-fb30-4241-800e-cc2b1dad9b19.png)
![看不见图片请使用科学上网](https://user-images.githubusercontent.com/22728201/157242314-5716d578-fee5-4083-8d91-0d98cb2545d9.png)
### 📌 TODO
* [x] 支持第三方云储存,本地、阿里云 OSS腾讯云 COS、七牛云、又拍云、FTP
* [x] 多图上传、拖拽上传、粘贴上传、上传预览、全屏预览、页面响应式布局
* [x] 简洁的图片管理功能,支持鼠标右键、单选多选、重命名等操作
* [x] 全局配置用户初始剩余储存空间、设置指定用户剩余储存空间
* [x] 一键复制图片外链、二维码扫描链接、图片鉴黄功能
* [x] 设置上传文件、文件夹路径命名规则、文件夹分类功能
* [x] 接口上传、图片软删除
* [x] OTA 平滑升级系统
* [x] (Dark)暗黑主题
* [x] IP 封禁功能
* [x] 自定义链接参数
* [x] 单用户模式
* [ ] 图片广场
...
* [x] 支持`本地`等多种第三方云储存 `AWS S3``阿里云 OSS``腾讯云 COS``七牛云``又拍云``SFTP``FTP``WebDav``Minio`
* [x] 多种数据库驱动支持,`MySQL 5.7+``PostgreSQL 9.6+``SQLite 3.8.8+``SQL Server 2017+`
* [x] 支持配置使用多种缓存驱动,`Memcached``Redis``DynamoDB`、等其他关系型数据库,默认以文件的方式缓存
* [x] 多图上传、拖拽上传、粘贴上传、动态设置策略上传、复制、一键复制链接
* [x] 强大的图片管理功能,瀑布流展示,支持鼠标右键、单选多选、重命名等操作
* [x] 自由度极高的角色组配置,可以为每个组配置多个储存策略,同时储存策略可以配置多个角色组
* [x] 可针对角色组设置上传文件、文件夹路径命名规则、上传频率限制、图片审核等功能
* [x] 支持图片水印、文字水印、水印平铺、设置水印位置、X/y 轴偏移量设置、旋转角度等
* [x] 支持通过接口上传、管理图片、管理相册
* [x] 支持在线增量更新、跨版本更新
* [x] 图片广场
### 🛠 安装要求
* PHP 版本 &ge; 5.6(&le; 7.3)
* mysql 版本 &ge; 5.5
* PDO 拓
* ZipArchive 支持
* fileinfo
* curl 拓
- PHP >= 8.0.2
- BCMath PHP 扩展
- Ctype PHP 扩
- DOM PHP 拓展
- Fileinfo PHP 扩
- JSON PHP 扩
- Mbstring PHP 扩展
- OpenSSL PHP 扩展
- PDO PHP 扩展
- Tokenizer PHP 扩展
- XML PHP 扩展
- Imagick 拓展
- exec、shell_exec 函数
- readlink、symlink 函数
- putenv、getenv 函数
- chmod、chown、fileperms 函数
注:推荐使用 PHP 7.3, 如果使用 FTP 功能,需要开启 PHP 的 FTP 拓展
### 🔍 安装教程
1. 下载兰空,上传至 web 运行环境,解压。
2. 设置运行目录为 public。
3. 配置 Rewrite 规则:
##### Nginx
```
location / {
if (!-e $request_filename) {
rewrite ^(.*)$ /index.php?s=$1 last; break;
}
}
```
##### Apache:
Apache 直接使用 .htaccess 即可
4. 访问首页,未安装自动跳转至安装页面,根据页面提示安装即可。
5. 安装完成以后请设置 runtime 目录 0755 权限如果你使用本地存储public 目录也需要设置为 0755 权限
### 📧 联系我
- Email: i@wispx.cn
### 😋 鸣谢
- [Laravel](https://laravel.com)
- [Tailwindcss](https://tailwindcss.com)
- [Fontawesome](https://fontawesome.com)
- [Echarts](https://echarts.apache.org)
- [Intervention/image](https://github.com/Intervention/image)
- [league/flysystem](https://flysystem.thephpleague.com)
- [overtrue](https://github.com/overtrue)
- [Jquery](https://jquery.com)
- [jQuery-File-Upload](https://github.com/blueimp/jQuery-File-Upload)
- [Alpinejs](https://alpinejs.dev/)
- [Viewer.js](https://github.com/fengyuanchen/viewerjs)
- [DragSelect](https://github.com/ThibaultJanBeyer/DragSelect)
- [Justified-Gallery](https://github.com/miromannino/Justified-Gallery)
- [Clipboard.js](https://github.com/zenorocha/clipboard.js)
### 💰 捐赠
Lsky Pro 的开发和更新等,都是作者在余时间独立开发,并免费开源使用,如果您认可我的作品,并且觉得对你有所帮助我愿意接受来自各方面的捐赠😃。
Lsky Pro 的开发和更新等,都是作者在余时间独立开发,并免费开源使用,如果您认可我的作品,并且觉得对你有所帮助我愿意接受来自各方面的捐赠😃。
<table width="100%">
<tr>
<th>支付宝</th>
<th>微信</th>
</tr>
<tr>
<td><img src="./public/static/app/images/demo/alipay.png?t=201911251121"></td>
<td><img src="./public/static/app/images/demo/wechat.jpeg?t=201911251121"></td>
<td><img alt="看不见图片请使用科学上网" src="https://raw.githubusercontent.com/lsky-org/lsky-pro/82988ebe2edd32264d609b26bf9132b3dce7c39e/public/static/app/images/demo/alipay.png"></td>
<td><img alt="看不见图片请使用科学上网" src="https://raw.githubusercontent.com/lsky-org/lsky-pro/82988ebe2edd32264d609b26bf9132b3dce7c39e/public/static/app/images/demo/wechat.jpeg"></td>
</tr>
</table>
### 😋 鸣谢
- ThinkPHP
- Jquery
- BootStrap
- Mdui
- viewer.js
- context.js
### 🤩 Stargazers over time
[![Stargazers over time](https://starchart.cc/lsky-org/lsky-pro.svg)](https://starchart.cc/lsky-org/lsky-pro)
### 📧 联系我
- Email: i@wispx.cn
### 📃 开源许可
[GPL 3.0](https://opensource.org/licenses/GPL-3.0)
Copyright (c) 2018-present Lsky Pro.

View File

@@ -0,0 +1,113 @@
<?php
namespace App\Console\Commands;
use App\Utils;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Artisan;
use Illuminate\Support\Facades\Config;
class Install extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'lsky:install';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Install Lsky Pro.';
/**
* Create a new command instance.
*
* @return void
*/
public function __construct()
{
$this->signature = implode(' ', [
'lsky:install',
'{--connection=mysql : Database type}',
'{--host=127.0.0.1 : Database connection address}',
'{--port=3306 : Database connection port}',
'{--database= : Database name}',
'{--username=root : Database connection user name}',
'{--password=root : Database connection password}',
]);
parent::__construct();
}
/**
* Execute the console command.
*
* @return int
*/
public function handle(): int
{
// 判断是否已经安装
if (file_exists(base_path('installed.lock'))) {
$this->warn('Already installed. if you want to reinstall, please remove installed.lock file.');
return 0;
}
$driver = $this->option('connection');
$connection = "database.connections.{$driver}";
$options = [
'connection' => $this->option('connection'),
'host' => $this->option('host'),
'port' => $this->option('port'),
'database' => $this->option('database'),
'username' => $this->option('username'),
'password' => $this->option('password'),
];
$configs = array_intersect_key($options, config($connection));
// 覆盖默认配置
Config::set($connection, array_merge(config($connection), $configs));
// 设置默认数据库驱动
Config::set('database.default', $driver);
clearstatcache(true);
try {
if ($options['connection'] === 'sqlite' && ! $options['database']) {
$options['database'] = database_path('database.sqlite');
file_put_contents($options['database'], '');
Config::set('database.connections.sqlite.database', $options['database']);
}
// 执行数据库迁移
Artisan::call('migrate:fresh', ['--force' => true], outputBuffer: $this->output);
// 填充数据
Artisan::call('db:seed', ['--force' => true, '--class' => 'InstallSeeder'], outputBuffer: $this->output);
// 更新 env 文件
$replaces = collect($options)->transform(fn ($item, $key) => ['DB_'.strtoupper($key) => $item])->collapse();
file_put_contents($this->laravel->environmentFilePath(), preg_replace(
$replaces->map(fn ($item, $key) => $this->replacementPattern($key, env($key, '')))->values()->toArray(),
$replaces->map(fn ($item, $key) => "{$key}={$item}")->values()->toArray(),
file_get_contents($this->laravel->environmentFilePath())
));
// 创建锁文件
file_put_contents(base_path('installed.lock'), '');
} catch (\Throwable $e) {
$this->warn("Installation error!\n");
$this->error($e->getMessage());
Utils::e($e, '执行数据库安装程序时出现异常');
return 1;
}
$this->info('Install success!');
return 0;
}
protected function replacementPattern(string $name, string $value): string
{
$escaped = preg_quote('='.$value, '/');
return "/^{$name}{$escaped}/m";
}
}

View File

@@ -0,0 +1,58 @@
<?php
namespace App\Console\Commands;
use App\Models\Image;
use App\Services\ImageService;
use Illuminate\Console\Command;
use League\Flysystem\FilesystemException;
use Symfony\Component\Console\Helper\ProgressBar;
class MakeThumbnails extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'lsky:thumbnails';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Make images thumbnails.';
/**
* Execute the console command.
*
* @return int
*/
public function handle()
{
$progress = new ProgressBar($this->output, Image::query()->count());
$progress->setMessage('获取图片处理中...');
$progress->start();
$service = new ImageService();
/** @var Image $image */
foreach (Image::query()->whereNotNull('strategy_id')->cursor() as $image) {
try {
$service->makeThumbnail(
image: $image,
data: $image->filesystem()->read($image->pathname),
force: true,
);
$progress->advance();
} catch (\Throwable $e) {
$this->error("缩略图生成失败, {$e->getMessage()}");
}
}
$progress->finish();
return 0;
}
}

View File

@@ -0,0 +1,45 @@
<?php
namespace App\Console\Commands;
use App\Enums\ConfigKey;
use App\Services\UpgradeService;
use App\Utils;
use Illuminate\Console\Command;
class Upgrade extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'lsky:upgrade';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Upgrade Lsky Pro.';
/**
* Create a new command instance.
*
* @return void
*/
public function __construct()
{
parent::__construct();
}
/**
* Execute the console command.
*
* @return int
*/
public function handle(): int
{
return (new UpgradeService(Utils::config(ConfigKey::AppVersion)))->upgrade() ? 0 : 1;
}
}

32
app/Console/Kernel.php Normal file
View File

@@ -0,0 +1,32 @@
<?php
namespace App\Console;
use Illuminate\Console\Scheduling\Schedule;
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
class Kernel extends ConsoleKernel
{
/**
* Define the application's command schedule.
*
* @param \Illuminate\Console\Scheduling\Schedule $schedule
* @return void
*/
protected function schedule(Schedule $schedule)
{
// $schedule->command('inspire')->hourly();
}
/**
* Register the commands for the application.
*
* @return void
*/
protected function commands()
{
$this->load(__DIR__.'/Commands');
require base_path('routes/console.php');
}
}

48
app/Enums/ConfigKey.php Normal file
View File

@@ -0,0 +1,48 @@
<?php
namespace App\Enums;
final class ConfigKey
{
/** @var string 是否启用注册 */
const IsEnableRegistration = 'is_enable_registration';
/** @var string 是否启用画廊 */
const IsEnableGallery = 'is_enable_gallery';
/** @var string 是否启用接口 */
const IsEnableApi = 'is_enable_api';
/** @var string 程序名称 */
const AppName = 'app_name';
/** @var string 程序版本 */
const AppVersion = 'app_version';
/** @var string 站点关键字 */
const SiteKeywords = 'site_keywords';
/** @var string 站点描述 */
const SiteDescription = 'site_description';
/** @var string 站点公告 */
const SiteNotice = 'site_notice';
/** @var string icp备案号 */
const IcpNo = 'icp_no';
/** @var string 是否允许游客上传 */
const IsAllowGuestUpload = 'is_allow_guest_upload';
/** @var string 用户初始容量(kb) */
const UserInitialCapacity = 'user_initial_capacity';
/** @var string 账户是否需要验证 */
const IsUserNeedVerify = 'is_user_need_verify';
/** @var string 邮件配置 */
const Mail = 'mail';
/** @var string 角色组默认配置 */
const Group = 'group';
}

View File

@@ -0,0 +1,63 @@
<?php
namespace App\Enums;
final class GroupConfigKey
{
/** @var string 最大文件大小 */
const MaximumFileSize = 'maximum_file_size';
/** @var string 并发上传数量 */
const ConcurrentUploadNum = 'concurrent_upload_num';
/** @var string 上传是否需要审查 */
const IsEnableScan = 'is_enable_scan';
/** @var string 审查配置 */
const ScanConfigs = 'scan_configs';
/** @var string 违规图片审查后动作 */
const ScannedAction = 'scanned_action';
/** @var string 是否启用原图保护功能 */
const IsEnableOriginalProtection = 'is_enable_original_protection';
/** @var string 上传是否启用水印功能 */
const IsEnableWatermark = 'is_enable_watermark';
/** @var string 水印配置 */
const WatermarkConfigs = 'watermark_configs';
/** @var string 每分钟上传限制 */
const LimitPerMinute = 'limit_per_minute';
/** @var string 每小时上传限制 */
const LimitPerHour = 'limit_per_hour';
/** @var string 每天上传数量限制 */
const LimitPerDay = 'limit_per_day';
/** @var string 每周上传数量限制 */
const LimitPerWeek = 'limit_per_week';
/** @var string 每月上传数量限制 */
const LimitPerMonth = 'limit_per_month';
/** @var string 允许上传的文件后缀 */
const AcceptedFileSuffixes = 'accepted_file_suffixes';
/** @var string 路径命名规则 */
const PathNamingRule = 'path_naming_rule';
/** @var string 文件命名规则 */
const FileNamingRule = 'file_naming_rule';
/** @var string 图片缓存时间 */
const ImageCacheTtl = 'image_cache_ttl';
/** @var string 图片保存格式 */
const ImageSaveFormat = 'image_save_format';
/** @var string 图片保存质量 */
const ImageSaveQuality = 'image_save_quality';
}

View File

@@ -0,0 +1,9 @@
<?php
namespace App\Enums;
final class ImagePermission
{
const Public = 1; // 公开
const Private = 0; // 私有
}

View File

@@ -0,0 +1,32 @@
<?php
namespace App\Enums\Mail;
final class SmtpOption
{
const Transport = 'smtp';
/** @var string 主机地址 */
const Host = 'host';
/** @var string 连接端口 */
const Port = 'port';
/** @var string 加密方式 */
const Encryption = 'encryption';
/** @var string 用户名 */
const Username = 'username';
/** @var string 密码 */
const Password = 'password';
/** @var string 超时时间 */
const Timeout = 'timeout';
/** @var string 发件人地址 */
const FromAddress = 'from_address';
/** @var string 发件人名称 */
const FromName = 'from_name';
}

View File

@@ -0,0 +1,9 @@
<?php
namespace App\Enums;
final class PastedAction
{
const Upload = 2; // 直接上传
const Waiting = 1; // 等待上传
}

View File

@@ -0,0 +1,21 @@
<?php
namespace App\Enums\Scan;
final class AliyunOption
{
/** @var string AccessKeyId */
const AccessKeyId = 'access_key_id';
/** @var string AccessKeySecret */
const AccessKeySecret = 'access_key_secret';
/** @var string RegionId */
const RegionId = 'region_id';
/** @var string 场景 */
const Scenes = 'scenes';
/** @var string 业务场景 */
const BizType = 'biz_type';
}

View File

@@ -0,0 +1,15 @@
<?php
namespace App\Enums\Scan;
final class NsfwJsOption
{
/** @var string 接口地址 */
const ApiUrl = 'api_url';
/** @var string 表单属性名称 */
const AttrName = 'attr_name';
/** @var string 阈值 */
const Threshold = 'threshold';
}

View File

@@ -0,0 +1,21 @@
<?php
namespace App\Enums\Scan;
final class TencentOption
{
/** @var string SecretId */
const SecretId = 'secret_id';
/** @var string SecretKey */
const SecretKey = 'secret_key';
/** @var string Region */
const Region = 'region';
/** @var string Endpoint */
const Endpoint = 'endpoint';
/** @var string 业务场景 */
const BizType = 'biz_type';
}

View File

@@ -0,0 +1,24 @@
<?php
namespace App\Enums\Strategy;
final class CosOption
{
/** @var string 访问地址 */
const Url = 'url';
/** @var string AppId */
const AppId = 'app_id';
/** @var string SecretId */
const SecretId = 'secret_id';
/** @var string SecretKey */
const SecretKey = 'secret_key';
/** @var string 所属地域 */
const Region = 'region';
/** @var string 储存桶名称 */
const Bucket = 'bucket';
}

View File

@@ -0,0 +1,30 @@
<?php
namespace App\Enums\Strategy;
final class FtpOption
{
/** @var string 访问url */
const Url = 'url';
/** @var string 根目录 */
const Root = 'root';
/** @var string 主机地址 */
const Host = 'host';
/** @var string 连接名称 */
const Username = 'username';
/** @var string 密码 */
const Password = 'password';
/** @var string 连接端口 */
const Port = 'port';
/** @var string 加密连接 */
const Ssl = 'ssl';
/** @var string 被动模式 */
const Passive = 'passive';
}

View File

@@ -0,0 +1,18 @@
<?php
namespace App\Enums\Strategy;
final class KodoOption
{
/** @var string 访问地址 */
const Url = 'url';
/** @var string AccessKey */
const AccessKey = 'access_key';
/** @var string SecretKey */
const SecretKey = 'secret_key';
/** @var string Bucket */
const Bucket = 'bucket';
}

View File

@@ -0,0 +1,12 @@
<?php
namespace App\Enums\Strategy;
final class LocalOption
{
/** @var string 访问url */
const Url = 'url';
/** @var string 根目录 */
const Root = 'root';
}

View File

@@ -0,0 +1,27 @@
<?php
namespace App\Enums\Strategy;
final class MinioOption
{
/** @var string 访问url */
const Url = 'url';
/** @var string AccessKey */
const AccessKey = 'access_key';
/** @var string SecretKey */
const SecretKey = 'secret_key';
/** @var string Endpoint */
const Endpoint = 'endpoint';
/** @var string 区域 */
const Region = 'region';
/** @var string Bucket */
const Bucket = 'bucket';
/** @var string BucketEndpoint */
const BucketEndpoint = 'bucket_endpoint';
}

View File

@@ -0,0 +1,21 @@
<?php
namespace App\Enums\Strategy;
final class OssOption
{
/** @var string 访问地址 */
const Url = 'url';
/** @var string AccessKeyId */
const AccessKeyId = 'access_key_id';
/** @var string AccessKeySecret */
const AccessKeySecret = 'access_key_secret';
/** @var string Endpoint */
const Endpoint = 'endpoint';
/** @var string 储存桶名称 */
const Bucket = 'bucket';
}

View File

@@ -0,0 +1,24 @@
<?php
namespace App\Enums\Strategy;
final class S3Option
{
/** @var string 访问url */
const Url = 'url';
/** @var string AccessKeyId */
const AccessKeyId = 'access_key_id';
/** @var string SecretAccessKey */
const SecretAccessKey = 'secret_access_key';
/** @var string Endpoint */
const Endpoint = 'endpoint';
/** @var string 区域 */
const Region = 'region';
/** @var string Bucket */
const Bucket = 'bucket';
}

View File

@@ -0,0 +1,33 @@
<?php
namespace App\Enums\Strategy;
final class SftpOption
{
/** @var string 访问url */
const Url = 'url';
/** @var string 根目录 */
const Root = 'root';
/** @var string 主机地址 */
const Host = 'host';
/** @var string 连接名称 */
const Username = 'username';
/** @var string 密码 */
const Password = 'password';
/** @var string 私钥 */
const PrivateKey = 'private_key';
/** @var string 密钥口令 */
const Passphrase = 'passphrase';
/** @var string 连接端口 */
const Port = 'port';
/** @var string 使用代理 */
const UseAgent = 'use_agent';
}

View File

@@ -0,0 +1,18 @@
<?php
namespace App\Enums\Strategy;
final class UssOption
{
/** @var string 访问地址 */
const Url = 'url';
/** @var string 操作员 */
const Operator = 'operator';
/** @var string 密码 */
const Password = 'password';
/** @var string 服务名称 */
const Service = 'service';
}

View File

@@ -0,0 +1,24 @@
<?php
namespace App\Enums\Strategy;
final class WebDavOption
{
/** @var string 访问url */
const Url = 'url';
/** @var string baseUri */
const BaseUri = 'base_uri';
/** @var string 用户名 */
const Username = 'username';
/** @var string 密码 */
const Password = 'password';
/** @var string 认证方式 */
const AuthType = 'auth_type';
/** @var string 地址前缀 */
const Prefix = 'prefix';
}

36
app/Enums/StrategyKey.php Normal file
View File

@@ -0,0 +1,36 @@
<?php
namespace App\Enums;
final class StrategyKey
{
/** @var int 本地 */
const Local = 1;
/** @var int S3 */
const S3 = 2;
/** @var int 阿里云 Oss */
const Oss = 3;
/** @var int 腾讯云 Cos */
const Cos = 4;
/** @var int 七牛云 Kodo */
const Kodo = 5;
/** @var int 又拍云 Uss */
const Uss = 6;
/** @var int Sftp */
const Sftp = 7;
/** @var int Ftp */
const Ftp = 8;
/** @var int WebDav */
const Webdav = 9;
/** @var int Minio */
const Minio = 10;
}

View File

@@ -0,0 +1,21 @@
<?php
namespace App\Enums;
final class UserConfigKey
{
/** @var string 默认相册 */
const DefaultAlbum = 'default_album';
/** @var string 默认策略 */
const DefaultStrategy = 'default_strategy';
/** @var string 默认权限 */
const DefaultPermission = 'default_permission';
/** @var string 图片粘贴后动作 */
const PastedAction = 'pasted_action';
/** @var string 上传是否自动清除预览 */
const IsAutoClearPreview = 'is_auto_clear_preview';
}

12
app/Enums/UserStatus.php Normal file
View File

@@ -0,0 +1,12 @@
<?php
namespace App\Enums;
final class UserStatus
{
/** @var int 正常 */
const Normal = 1;
/** @var int 冻结 */
const Frozen = 0;
}

View File

@@ -0,0 +1,30 @@
<?php
namespace App\Enums\Watermark;
final class FontOption
{
/** @var string 文本内容 */
const Text = 'text';
/** @var string 文字颜色 */
const Color = 'color';
/** @var string 字体大小 */
const Size = 'size';
/** @var string 字体文件路径 */
const Font = 'font';
/** @var string 水印位置 */
const Position = 'position';
/** @var string 旋转角度 */
const Angle = 'angle';
/** @var string X轴偏移量 */
const X = 'x';
/** @var string Y轴偏移量 */
const Y = 'y';
}

View File

@@ -0,0 +1,30 @@
<?php
namespace App\Enums\Watermark;
final class ImageOption
{
/** @var string 水印图片 */
const Image = 'image';
/** @var string 水印位置 */
const Position = 'position';
/** @var string 水印图片宽 */
const Width = 'width';
/** @var string 水印图片高 */
const Height = 'height';
/** @var string 旋转角度 */
const Rotate = 'rotate';
/** @var string 不透明度 */
const Opacity = 'opacity';
/** @var string X轴偏移量 */
const X = 'x';
/** @var string Y轴偏移量 */
const Y = 'y';
}

View File

@@ -0,0 +1,9 @@
<?php
namespace App\Enums\Watermark;
final class Mode
{
const Overlay = 1; // 覆盖原图
const Dynamic = 2; // 动态生成
}

View File

@@ -0,0 +1,57 @@
<?php
namespace App\Exceptions;
use App\Http\Result;
use Illuminate\Auth\AuthenticationException;
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
use Illuminate\Http\Exceptions\ThrottleRequestsException;
use Throwable;
class Handler extends ExceptionHandler
{
use Result;
/**
* A list of the exception types that are not reported.
*
* @var array<int, class-string<Throwable>>
*/
protected $dontReport = [
//
];
/**
* A list of the inputs that are never flashed for validation exceptions.
*
* @var array<int, string>
*/
protected $dontFlash = [
'current_password',
'password',
'password_confirmation',
];
/**
* Register the exception handling callbacks for the application.
*
* @return void
*/
public function register()
{
$this->reportable(function (Throwable $e) {
//
});
$this->renderable(function (ThrottleRequestsException $e) {
return $this->fail($e->getMessage())->setStatusCode(429);
});
}
protected function unauthenticated($request, AuthenticationException $exception)
{
return $this->shouldReturnJson($request, $exception)
? $this->fail($exception->getMessage())->setStatusCode(401)
: redirect()->guest($exception->redirectTo() ?? route('login'));
}
}

View File

@@ -0,0 +1,9 @@
<?php
namespace App\Exceptions;
use Exception;
class UploadException extends Exception
{
}

View File

@@ -0,0 +1,65 @@
<?php
namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller;
use App\Models\Image;
use App\Models\User;
use App\Utils;
use Illuminate\Support\Carbon;
use Illuminate\Support\Collection;
use Illuminate\View\View;
class ConsoleController extends Controller
{
public function index(): View
{
$carbon = Carbon::now();
$format = 'Y-m-d H:i:s';
$numbers = [
'today' => Image::query()->whereBetween('created_at', [$carbon->startOfDay()->format($format), $carbon->endOfDay()->format($format)])->count(),
'yesterday' => Image::query()->whereBetween('created_at', [$carbon->yesterday()->startOfDay()->format($format), $carbon->yesterday()->endOfDay()->format($format)])->count(),
'week' => Image::query()->whereBetween('created_at', [$carbon->startOfWeek()->format($format), $carbon->endOfWeek()->format($format)])->count(),
'month' => Image::query()->whereBetween('created_at', [$carbon->startOfMonth()->format($format), $carbon->endOfMonth()->format($format)])->count(),
];
$start = Carbon::now()->parse('-30 day')->startOfDay();
$end = Carbon::now()->endOfDay();
$dates = Utils::makeDateRange($start->format('Y-m-d'), $end->format('Y-m-d'));
$fields = ['游客上传', '用户上传', '新用户'];
$images = Image::query()
->whereBetween('created_at', [$start->format($format), $end->format($format)])
->get(['user_id', 'created_at'])
->transform(function (Image $image) {
$image['date'] = $image->created_at->format('Y-m-d');
return $image;
})->groupBy('date');
$users = User::query()
->whereBetween('created_at', [$start->format($format), $end->format($format)])
->get()
->transform(function (User $user) {
$user['date'] = $user->created_at->format('Y-m-d');
return $user;
})->groupBy('date');
$data = collect(array_map(fn() => 0, array_flip($dates)));
$array = [
$data->merge($images->map(fn(Collection $items) => $items->whereNull('user_id')->count())),
$data->merge($images->map(fn(Collection $items) => $items->whereNotNull('user_id')->count())),
$data->merge($users->map(fn(Collection $items) => $items->count())),
];
$datasets = collect($fields)->transform(function ($item, $index) use ($dates, $array) {
return [
'name' => $item,
'type' => 'line',
'data' => $array[$index]->values(),
];
});
return view('admin.console.index', compact('fields', 'numbers', 'dates', 'datasets'));
}
}

View File

@@ -0,0 +1,125 @@
<?php
declare(strict_types=1);
namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller;
use App\Http\Requests\Admin\GroupRequest;
use App\Models\Group;
use App\Models\Image;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\DB;
use Illuminate\View\View;
class GroupController extends Controller
{
private function share()
{
\Illuminate\Support\Facades\View::share([
'default' => Group::getDefaultConfigs(),
'positions' => Group::POSITIONS,
'scenes' => Group::SCENES,
]);
}
public function index(Request $request): View
{
$keywords = $request->query('keywords');
$groups = Group::query()->when($keywords, function (Builder $builder, $keywords) {
$builder->where('name', 'like', "%{$keywords}%");
})->withCount('users')->withCount('strategies')->latest()->paginate();
$groups->appends(compact('keywords'));
$this->share();
return view('admin.group.index', compact('groups'));
}
public function add(): View
{
$this->share();
return view('admin.group.add');
}
public function edit(Request $request): View
{
$group = Group::query()->findOrFail($request->route('id'));
$this->share();
return view('admin.group.edit', compact('group'));
}
public function create(GroupRequest $request): Response
{
DB::transaction(function () use ($request) {
$group = new Group();
$group->fill($request->validated());
$group->save();
});
$this->share();
return $this->success('创建成功');
}
public function update(GroupRequest $request): Response
{
DB::beginTransaction();
try {
/** @var Group $group */
$group = Group::query()->findOrFail($request->route('id'));
$group->fill($request->validated());
if ($group->isDirty('is_default') && ! $group->is_default) {
if (! Group::query()->where('is_default', true)->where('id', '<>', $group->id)->exists()) {
return $this->fail('系统至少需要保留一个默认组');
}
}
if ($group->isDirty('is_guest') && ! $group->is_guest) {
if (! Group::query()->where('is_guest', true)->where('id', '<>', $group->id)->exists()) {
return $this->fail('系统至少需要保留一个游客组');
}
}
$group->save();
DB::commit();
} catch (\Throwable $e) {
DB::rollBack();
return $this->fail('保存失败');
}
return $this->success('保存成功');
}
public function delete(Request $request): Response
{
/** @var Group $group */
if ($group = Group::query()->find($request->route('id'))) {
if ($group->is_default || $group->is_guest) {
return $this->fail('默认组和游客组无法删除');
}
DB::transaction(function () use ($group) {
$group->users()->update(['group_id' => Group::query()->where('is_default', true)->value('id')]);
$group->delete();
});
}
return $this->success('删除成功');
}
public function clearCache(Request $request): Response
{
/** @var Group $group */
if ($group = Group::query()->find($request->route('id'))) {
/** @var Image $image */
foreach ($group->images()->select('key')->cursor() as $image) {
Cache::forget('image_'.$image->key);
}
}
return $this->success('清除成功');
}
}

View File

@@ -0,0 +1,96 @@
<?php
namespace App\Http\Controllers\Admin;
use App\Enums\ImagePermission;
use App\Http\Controllers\Controller;
use App\Models\Image;
use App\Services\UserService;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Support\Str;
use Illuminate\View\View;
class ImageController extends Controller
{
public function index(Request $request): View
{
$keywords = $request->query('keywords');
$images = Image::query()->with(['user' => function (BelongsTo $belongsTo) {
$belongsTo->withSum('images', 'size');
}, 'album', 'group', 'strategy'])->when($keywords, function (Builder $builder, $keywords) {
$words = [];
$qualifiers = [
'name:', 'album:', 'group:', 'strategy:', 'email:', 'extension:', 'md5:', 'sha1:', 'ip:', 'is:', 'order:',
];
collect(array_filter(explode(' ', $keywords)))->filter(function ($keyword) use ($qualifiers, &$words) {
if (Str::startsWith($keyword, $qualifiers)) {
return true;
}
$words[] = $keyword;
return false;
})->each(function ($filter) use ($builder) {
match ($filter) {
'is:public' => $builder->where('permission', ImagePermission::Public),
'is:private' => $builder->where('permission', ImagePermission::Private),
'is:unhealthy' => $builder->where('is_unhealthy', 1),
'is:guest' => $builder->whereNull('user_id'),
'is:adminer' => $builder->whereHas('user', fn (Builder $builder) => $builder->where('is_adminer', 1)),
'order:earliest' => $builder->orderBy('created_at'),
'order:utmost' => $builder->orderByDesc('size'),
'order:least' => $builder->orderBy('size'),
default => 0,
};
[$qualifier, $value] = explode(':', $filter);
if ($value) {
$callback = fn (Builder $builder) => $builder->where('name', $value);
match ($qualifier) {
'name' => $builder->whereHas('user', $callback),
'album' => $builder->whereHas('album', $callback),
'group' => $builder->whereHas('group', $callback),
'strategy' => $builder->whereHas('strategy', $callback),
'email' => $builder->whereHas('user', fn (Builder $builder) => $builder->where('email', $value)),
'extension' => $builder->where('extension', $value),
'md5' => $builder->where('md5', $value),
'sha1' => $builder->where('sha1', $value),
'ip' => $builder->where('ip', $value),
default => 0
};
}
});
foreach ($words as $word) {
$builder->where('name', 'like', "%{$word}%")
->orWhere('origin_name', 'like', "%{$word}%")
->orWhere('alias_name', 'like', "%{$word}%");
}
})->latest()->paginate(40);
$images->getCollection()->each(function (Image $image) {
$image->append('url', 'pathname', 'thumb_url');
$image->album?->setVisible(['name']);
$image->group?->setVisible(['name']);
$image->strategy?->setVisible(['name']);
});
$images->appends(compact('keywords'));
return view('admin.image.index', compact('images'));
}
public function update(): Response
{
return $this->success();
}
public function delete(Request $request): Response
{
/** @var Image $image */
$image = Image::with('user', 'strategy', 'album')->find($request->route('id'));
(new UserService())->deleteImages([$image->id]);
return $this->success('删除成功');
}
}

View File

@@ -0,0 +1,78 @@
<?php
namespace App\Http\Controllers\Admin;
use App\Enums\ConfigKey;
use App\Http\Controllers\Controller;
use App\Mail\Test;
use App\Models\Config;
use App\Services\UpgradeService;
use App\Utils;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Mail;
use Illuminate\View\View;
class SettingController extends Controller
{
public function index(): View
{
$configs = Utils::config();
return view('admin.setting.index', compact('configs'));
}
public function save(Request $request): Response
{
foreach ($request->all() as $key => $value) {
Config::query()->where('name', $key)->update(['value' => $value]);
}
Cache::flush();
return $this->success('保存成功');
}
public function mailTest(Request $request): Response
{
try {
Mail::to($request->post('email'))->send(new Test());
} catch (\Throwable $e) {
return $this->fail($e->getMessage());
}
return $this->success('发送成功');
}
public function checkUpdate(): Response
{
$version = Utils::config(ConfigKey::AppVersion);
$service = new UpgradeService($version);
try {
$data = [
'is_update' => $service->check(),
];
if ($data['is_update']) {
$data['version'] = $service->getVersions()->first();
}
} catch (\Exception $e) {
return $this->fail($e->getMessage());
}
return $this->success('success', $data);
}
public function upgrade()
{
ignore_user_abort(true);
set_time_limit(0);
$version = Utils::config(ConfigKey::AppVersion);
$service = new UpgradeService($version);
$this->success()->send();
$service->upgrade();
flush();
}
public function upgradeProgress(): Response
{
return $this->success('success', Cache::get('upgrade_progress'));
}
}

View File

@@ -0,0 +1,75 @@
<?php
namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller;
use App\Http\Requests\Admin\StrategyRequest;
use App\Models\Strategy;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Support\Facades\DB;
use Illuminate\View\View;
class StrategyController extends Controller
{
public function index(Request $request): View
{
$keywords = $request->query('keywords');
$strategies = Strategy::query()->when($keywords, function (Builder $builder, $keywords) {
$builder->where('name', 'like', "%{$keywords}%")->orWhere('intro', 'like', "%{$keywords}%");
})->withCount('images')->withSum('images', 'size')->latest()->paginate();
$strategies->appends(compact('keywords'));
return view('admin.strategy.index', compact('strategies'));
}
public function add(): View
{
return view('admin.strategy.add');
}
public function edit(Request $request): View
{
/** @var Strategy $strategy */
$strategy = Strategy::query()->findOrFail($request->route('id'));
return view('admin.strategy.edit', compact('strategy'));
}
public function create(StrategyRequest $request): Response
{
$validated = $request->validated();
$strategy = new Strategy($validated);
DB::transaction(function () use ($strategy, $validated) {
$strategy->save();
$strategy->groups()->attach($validated['groups'] ?? []);
});
return $this->success('创建成功');
}
public function update(StrategyRequest $request): Response
{
$validated = $request->validated();
/** @var Strategy $strategy */
$strategy = Strategy::query()->findOrFail($request->route('id'));
$strategy->fill($request->validated());
DB::transaction(function () use ($strategy, $validated) {
$strategy->save();
$strategy->groups()->sync($validated['groups'] ?? []);
});
return $this->success('保存成功');
}
public function delete(Request $request): Response
{
/** @var Strategy $strategy */
if ($strategy = Strategy::query()->find($request->route('id'))) {
DB::transaction(function () use ($strategy) {
$strategy->images()->update(['strategy_id' => null]);
$strategy->delete();
});
}
return $this->success('删除成功');
}
}

View File

@@ -0,0 +1,81 @@
<?php
namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller;
use App\Http\Requests\Admin\UserRequest;
use App\Models\User;
use Illuminate\Auth\Events\PasswordReset;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Str;
use Illuminate\View\View;
class UserController extends Controller
{
public function index(Request $request): View
{
$status = $request->query('status');
$keywords = $request->query('keywords');
$users = User::query()->when($status > -1, function (Builder $builder) use ($status) {
$builder->where('status', $status);
})->when($keywords, function (Builder $builder, $keywords) {
$builder->where('name', 'like', "%{$keywords}%")->orWhere('email', 'like', "%{$keywords}%");
})->with('group')->withSum('images', 'size')->latest()->paginate();
$users->getCollection()->each(function (User $user) {
$user->group->setVisible(['name']);
});
$statuses = [-1 => '全部', 1 => '正常', 0 => '冻结'];
$users->appends(compact('status', 'keywords'));
return view('admin.user.index', compact('users', 'statuses'));
}
public function edit(Request $request): View
{
$user = User::query()->findOrFail($request->route('id'));
return view('admin.user.edit', compact('user'));
}
public function update(UserRequest $request): Response
{
/** @var User $user */
$user = User::query()->findOrFail($request->route('id'));
$validated = $request->validated();
if (! empty($validated['password'])) {
$user->forceFill([
'password' => Hash::make($validated['password']),
'remember_token' => Str::random(60),
]);
event(new PasswordReset($user));
}
unset($validated['password']);
$user->fill($validated);
$user->group_id = $validated['group_id'];
if (!$user->save()) {
return $this->fail('保存失败');
}
return $this->success('保存成功');
}
public function delete(Request $request): Response
{
/** @var User $user */
if ($user = User::query()->find($request->route('id'))) {
DB::transaction(function () use ($user) {
$user->images()->update(['user_id' => null]);
$user->albums()->delete();
$user->delete();
});
}
return $this->success('删除成功');
}
}

View File

@@ -0,0 +1,32 @@
<?php
namespace App\Http\Controllers\Api\V1;
use App\Http\Controllers\Controller;
use App\Models\Album;
use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Support\Facades\Auth;
class AlbumController extends Controller
{
public function index(Request $request): Response
{
/** @var User $user */
$user = Auth::user();
$albums = $user->albums()->filter($request)->paginate(40);
$albums->getCollection()->each(function (Album $album) {
$album->setVisible(['id', 'name', 'intro', 'image_num']);
});
return $this->success('success', $albums);
}
public function destroy(Request $request): Response
{
/** @var User $user */
$user = Auth::user();
$user->albums()->where('id', $request->route('id'))->delete();
return $this->success('删除成功');
}
}

View File

@@ -0,0 +1,83 @@
<?php
namespace App\Http\Controllers\Api\V1;
use App\Exceptions\UploadException;
use App\Http\Controllers\Controller;
use App\Models\Image;
use App\Models\User;
use App\Services\ImageService;
use App\Services\UserService;
use App\Utils;
use Illuminate\Auth\AuthenticationException;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Support\Facades\Auth;
class ImageController extends Controller
{
/**
* @throws AuthenticationException
*/
public function upload(Request $request, ImageService $service): Response
{
if ($request->hasHeader('Authorization')) {
$guards = array_keys(config('auth.guards'));
if (empty($guards)) {
$guards = [null];
}
foreach ($guards as $guard) {
if (Auth::guard($guard)->check()) {
Auth::shouldUse($guard);
break;
}
}
if (! Auth::check()) {
throw new AuthenticationException('Authentication failed.');
}
}
try {
$image = $service->store($request);
} catch (UploadException $e) {
return $this->fail($e->getMessage());
} catch (\Throwable $e) {
Utils::e($e, 'Api 上传文件时发生异常');
if (config('app.debug')) {
return $this->fail($e->getMessage());
}
return $this->fail('服务异常,请稍后再试');
}
return $this->success('上传成功', $image->setAppends(['pathname', 'links'])->only(
'key', 'name', 'pathname', 'origin_name', 'size', 'mimetype', 'extension', 'md5', 'sha1', 'links'
));
}
public function images(Request $request): Response
{
/** @var User $user */
$user = Auth::user();
$images = $user->images()->filter($request)->paginate(40)->withQueryString();
$images->getCollection()->each(function (Image $image) {
$image->human_date = $image->created_at->diffForHumans();
$image->date = $image->created_at->format('Y-m-d H:i:s');
$image->append(['pathname', 'links'])->setVisible([
'album', 'key', 'name', 'pathname', 'origin_name', 'size', 'mimetype', 'extension', 'md5', 'sha1',
'width', 'height', 'links', 'human_date', 'date',
]);
});
return $this->success('success', $images);
}
public function destroy(Request $request): Response
{
/** @var User $user */
$user = Auth::user();
(new UserService())->deleteImages([$request->route('key')], $user, 'key');
return $this->success('删除成功');
}
}

View File

@@ -0,0 +1,20 @@
<?php
namespace App\Http\Controllers\Api\V1;
use App\Http\Controllers\Controller;
use App\Models\Group;
use App\Models\Strategy;
use Illuminate\Http\Response;
use Illuminate\Support\Facades\Auth;
class StrategyController extends Controller
{
public function index(): Response
{
/** @var Group $group */
$group = Auth::check() ? Auth::user()->group : Group::query()->where('is_guest', true)->first();
$strategies = $group->strategies()->get()->each(fn (Strategy $strategy) => $strategy->setVisible(['id', 'name']));
return $this->success('success', compact('strategies'));
}
}

View File

@@ -0,0 +1,45 @@
<?php
namespace App\Http\Controllers\Api\V1;
use App\Http\Controllers\Controller;
use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Hash;
use Illuminate\Validation\ValidationException;
class TokenController extends Controller
{
public function store(Request $request): Response
{
try {
$request->validate([
'email' => 'required|email',
'password' => 'required',
]);
} catch (ValidationException $e) {
return $this->fail($e->validator->errors()->first());
}
/** @var User|null $user */
$user = User::query()->where('email', $request->email)->first();
if (! $user || ! Hash::check($request->password, $user->password)) {
return $this->fail('The email address or password is incorrect.');
}
$token = $user->createToken($user->email)->plainTextToken;
return $this->success('success', compact('token'));
}
public function clear(): Response
{
/** @var User $user */
$user = Auth::user();
$user->tokens()->delete();
return $this->success();
}
}

View File

@@ -0,0 +1,22 @@
<?php
namespace App\Http\Controllers\Api\V1;
use App\Http\Controllers\Controller;
use App\Models\User;
use Illuminate\Http\Response;
use Illuminate\Support\Facades\Auth;
class UserController extends Controller
{
public function index(): Response
{
/** @var User $user */
$user = Auth::user();
$user->used_capacity = $user->images()->sum('size') + 0;
$user->setVisible([
'name', 'avatar', 'email', 'capacity', 'used_capacity', 'url', 'image_num', 'album_num', 'registered_ip'
]);
return $this->success('success', $user);
}
}

View File

@@ -0,0 +1,54 @@
<?php
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use App\Http\Requests\Auth\LoginRequest;
use App\Providers\RouteServiceProvider;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
class AuthenticatedSessionController extends Controller
{
/**
* Display the login view.
*
* @return \Illuminate\View\View
*/
public function create()
{
return view('auth.login');
}
/**
* Handle an incoming authentication request.
*
* @param \App\Http\Requests\Auth\LoginRequest $request
* @return \Illuminate\Http\RedirectResponse
*/
public function store(LoginRequest $request)
{
$request->authenticate();
$request->session()->regenerate();
return redirect()->intended(RouteServiceProvider::HOME);
}
/**
* Destroy an authenticated session.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\RedirectResponse
*/
public function destroy(Request $request)
{
Auth::guard('web')->logout();
$request->session()->invalidate();
$request->session()->regenerateToken();
return redirect('/');
}
}

View File

@@ -0,0 +1,44 @@
<?php
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use App\Providers\RouteServiceProvider;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Validation\ValidationException;
class ConfirmablePasswordController extends Controller
{
/**
* Show the confirm password view.
*
* @return \Illuminate\View\View
*/
public function show()
{
return view('auth.confirm-password');
}
/**
* Confirm the user's password.
*
* @param \Illuminate\Http\Request $request
* @return mixed
*/
public function store(Request $request)
{
if (! Auth::guard('web')->validate([
'email' => $request->user()->email,
'password' => $request->password,
])) {
throw ValidationException::withMessages([
'password' => __('auth.password'),
]);
}
$request->session()->put('auth.password_confirmed_at', time());
return redirect()->intended(RouteServiceProvider::HOME);
}
}

View File

@@ -0,0 +1,27 @@
<?php
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use App\Providers\RouteServiceProvider;
use Illuminate\Http\Request;
class EmailVerificationNotificationController extends Controller
{
/**
* Send a new email verification notification.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\RedirectResponse
*/
public function store(Request $request)
{
if ($request->user()->hasVerifiedEmail()) {
return redirect()->intended(RouteServiceProvider::HOME);
}
$request->user()->sendEmailVerificationNotification();
return back()->with('status', 'verification-link-sent');
}
}

View File

@@ -0,0 +1,23 @@
<?php
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use App\Providers\RouteServiceProvider;
use Illuminate\Http\Request;
class EmailVerificationPromptController extends Controller
{
/**
* Display the email verification prompt.
*
* @param \Illuminate\Http\Request $request
* @return mixed
*/
public function __invoke(Request $request)
{
return $request->user()->hasVerifiedEmail()
? redirect()->intended(RouteServiceProvider::HOME)
: view('auth.verify-email');
}
}

View File

@@ -0,0 +1,65 @@
<?php
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use Illuminate\Auth\Events\PasswordReset;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Password;
use Illuminate\Support\Str;
use Illuminate\Validation\Rules;
class NewPasswordController extends Controller
{
/**
* Display the password reset view.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\View\View
*/
public function create(Request $request)
{
return view('auth.reset-password', ['request' => $request]);
}
/**
* Handle an incoming new password request.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\RedirectResponse
*
* @throws \Illuminate\Validation\ValidationException
*/
public function store(Request $request)
{
$request->validate([
'token' => ['required'],
'email' => ['required', 'email'],
'password' => ['required', 'confirmed', Rules\Password::defaults()],
]);
// Here we will attempt to reset the user's password. If it is successful we
// will update the password on an actual user model and persist it to the
// database. Otherwise we will parse the error and return the response.
$status = Password::reset(
$request->only('email', 'password', 'password_confirmation', 'token'),
function ($user) use ($request) {
$user->forceFill([
'password' => Hash::make($request->password),
'remember_token' => Str::random(60),
])->save();
event(new PasswordReset($user));
}
);
// If the password was successfully reset, we will redirect the user back to
// the application's home authenticated view. If there is an error we can
// redirect them back to where they came from with their error message.
return $status == Password::PASSWORD_RESET
? redirect()->route('login')->with('status', __($status))
: back()->withInput($request->only('email'))
->withErrors(['email' => __($status)]);
}
}

View File

@@ -0,0 +1,47 @@
<?php
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Password;
class PasswordResetLinkController extends Controller
{
/**
* Display the password reset link request view.
*
* @return \Illuminate\View\View
*/
public function create()
{
return view('auth.forgot-password');
}
/**
* Handle an incoming password reset link request.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\RedirectResponse
*
* @throws \Illuminate\Validation\ValidationException
*/
public function store(Request $request)
{
$request->validate([
'email' => ['required', 'email'],
]);
// We will send the password reset link to this user. Once we have attempted
// to send the link, we will examine the response then see the message we
// need to show to the user. Finally, we'll send out a proper response.
$status = Password::sendResetLink(
$request->only('email')
);
return $status == Password::RESET_LINK_SENT
? back()->with('status', __($status))
: back()->withInput($request->only('email'))
->withErrors(['email' => __($status)]);
}
}

View File

@@ -0,0 +1,59 @@
<?php
namespace App\Http\Controllers\Auth;
use App\Enums\ConfigKey;
use App\Http\Controllers\Controller;
use App\Models\User;
use App\Providers\RouteServiceProvider;
use App\Utils;
use Illuminate\Auth\Events\Registered;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Hash;
use Illuminate\Validation\Rules;
class RegisteredUserController extends Controller
{
/**
* Display the registration view.
*
* @return \Illuminate\View\View
*/
public function create()
{
return view('auth.register');
}
/**
* Handle an incoming registration request.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\RedirectResponse
*
* @throws \Illuminate\Validation\ValidationException
*/
public function store(Request $request)
{
$request->validate([
'name' => ['required', 'string', 'max:255'],
'email' => ['required', 'string', 'email', 'max:255', 'unique:users'],
'password' => ['required', 'confirmed', Rules\Password::defaults()],
]);
$user = User::create([
'name' => $request->name,
'email' => $request->email,
'password' => Hash::make($request->password),
'registered_ip' => $request->ip(),
]);
if (Utils::config(ConfigKey::IsUserNeedVerify)) {
event(new Registered($user));
}
Auth::login($user);
return redirect(RouteServiceProvider::HOME);
}
}

View File

@@ -0,0 +1,30 @@
<?php
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use App\Providers\RouteServiceProvider;
use Illuminate\Auth\Events\Verified;
use Illuminate\Foundation\Auth\EmailVerificationRequest;
class VerifyEmailController extends Controller
{
/**
* Mark the authenticated user's email address as verified.
*
* @param \Illuminate\Foundation\Auth\EmailVerificationRequest $request
* @return \Illuminate\Http\RedirectResponse
*/
public function __invoke(EmailVerificationRequest $request)
{
if ($request->user()->hasVerifiedEmail()) {
return redirect()->intended(RouteServiceProvider::HOME.'?verified=1');
}
if ($request->user()->markEmailAsVerified()) {
event(new Verified($request->user()));
}
return redirect()->intended(RouteServiceProvider::HOME.'?verified=1');
}
}

View File

@@ -0,0 +1,14 @@
<?php
namespace App\Http\Controllers\Common;
use App\Http\Controllers\Controller;
use Illuminate\View\View;
class ApiController extends Controller
{
public function index(): View
{
return view('common.api');
}
}

View File

@@ -0,0 +1,23 @@
<?php
namespace App\Http\Controllers\Common;
use App\Enums\ImagePermission;
use App\Http\Controllers\Controller;
use App\Models\Image;
use Illuminate\View\View;
class GalleryController extends Controller
{
public function index(): View
{
$images = Image::query()
->with('user')
->whereNotNull('user_id')
->where('is_unhealthy', false)
->where('permission', ImagePermission::Public)
->latest()
->simplePaginate(40);
return view('common.gallery', compact('images'));
}
}

View File

@@ -0,0 +1,205 @@
<?php
namespace App\Http\Controllers;
use App\Enums\GroupConfigKey;
use App\Enums\UserStatus;
use App\Enums\Watermark\Mode;
use App\Exceptions\UploadException;
use App\Http\Result;
use App\Models\Group;
use App\Models\Image;
use App\Models\Strategy;
use App\Models\User;
use App\Services\ImageService;
use App\Utils;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Foundation\Bus\DispatchesJobs;
use Illuminate\Foundation\Validation\ValidatesRequests;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Routing\Controller as BaseController;
use Illuminate\Support\Facades\Artisan;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Hash;
use Illuminate\View\View;
use Intervention\Image\Facades\Image as InterventionImage;
use League\Flysystem\FilesystemException;
use Symfony\Component\Console\Output\BufferedOutput;
use Symfony\Component\HttpFoundation\StreamedResponse;
class Controller extends BaseController
{
use AuthorizesRequests, DispatchesJobs, ValidatesRequests, Result;
public function install(Request $request): View|Response
{
if (file_exists(base_path('installed.lock'))) {
if ($request->expectsJson()) {
return $this->fail('Already installed. if you want to reinstall, please remove installed.lock file.');
}
abort(404);
}
$extensions = collect([
['name' => 'BCMath', 'intro' => '数学精度处理拓展'],
['name' => 'Ctype', 'intro' => '字符类型检测拓展'],
['name' => 'DOM', 'intro' => 'DOM 对象模型,用于处理文档元素'],
['name' => 'Fileinfo', 'intro' => '获取文件信息拓展'],
['name' => 'JSON', 'intro' => 'JavaScript 对象符号JSON'],
['name' => 'Mbstring', 'intro' => '多字节字符串处理拓展'],
['name' => 'OpenSSL', 'intro' => '加密拓展'],
['name' => 'PCRE', 'intro' => '正则表达式拓展'],
['name' => 'PDO', 'intro' => '数据库拓展'],
['name' => 'Tokenizer', 'intro' => '令牌处理拓展'],
['name' => 'XML', 'intro' => 'Xml 解析器'],
['name' => 'Imagick', 'intro' => '高性能图片处理拓展'],
])->transform(function ($item) {
$item['result'] = extension_loaded(strtolower($item['name']));
return $item;
})->push([
'name' => 'readlink、symlink 函数',
'intro' => '读取、创建符号链接函数',
'result' => function_exists('readlink') && function_exists('symlink'),
])->push([
'name' => 'putenv、getenv 函数',
'intro' => '设置和获取环境变量函数',
'result' => function_exists('putenv') && function_exists('getenv'),
])->push([
'name' => 'exec、shell_exec 函数',
'intro' => '执行外部命令',
'result' => function_exists('exec') && function_exists('shell_exec'),
])->push([
'name' => 'chmod、chown、fileperms 函数',
'intro' => '设置和获取文件、文件夹权限函数',
'result' => function_exists('chmod') && function_exists('chown') && function_exists('fileperms'),
])->push([
'name' => 'PHP >= 8.0.2',
'intro' => '最低要求 PHP 8.0.2 版本',
'result' => phpversion() >= 8,
]);
$status = ! $extensions->where('result', false)->isNotEmpty();
if ($request->method() === 'POST') {
try {
$request->validate([
'account.email' => 'required|email',
'account.password' => 'required|between:6,32'
], [], [
'account.email' => '管理员账号邮箱',
'account.password' => '管理员账号密码'
]);
$data = collect($request->only([
'connection', 'host', 'port', 'database', 'username', 'password',
]))->transform(fn($item, $key) => ['--'.$key => $item])->collapse();
$output = new BufferedOutput();
$exitCode = Artisan::call('lsky:install', $data->toArray(), $output);
if ($exitCode) {
throw new \Exception(str_replace(PHP_EOL, '<br/>', $output->fetch()));
}
$user = new User([
'name' => '超级管理员',
'email' => $request->input('account.email'),
'password' => Hash::make($request->input('account.password')),
'registered_ip' => $request->ip(),
]);
$user->group_id = Group::query()->first()['id'];
$user->is_adminer = true;
$user->status = UserStatus::Normal;
$user->email_verified_at = date('Y-m-d H:i:s');
// 设置默认策略组的 url 为当前请求 url
Strategy::query()->update(['configs->url' => $request->getSchemeAndHttpHost().'/i']);
$user->save();
} catch (\Throwable $e) {
@unlink(base_path('installed.lock'));
Utils::e($e, '执行安装程序时出现异常');
return $this->fail($e->getMessage());
}
return $this->success();
}
return view('install', compact('extensions', 'status'));
}
public function upload(Request $request, ImageService $service): Response
{
try {
$image = $service->store($request);
} catch (UploadException $e) {
return $this->fail($e->getMessage());
} catch (\Throwable $e) {
Utils::e($e, 'Web 上传文件时发生异常');
if (config('app.debug')) {
return $this->fail($e->getMessage());
}
return $this->fail('服务异常,请稍后再试');
}
return $this->success('上传成功', $image->setAppends(['pathname', 'links'])->only(
'id', 'pathname', 'origin_name', 'size', 'mimetype', 'md5', 'sha1', 'links'
));
}
public function output(Request $request, ImageService $service): StreamedResponse
{
/** @var Image $image */
$image = Image::query()
->with('group')
->where('key', $request->route('key'))
->where('extension', strtolower($request->route('extension')))
->firstOr(fn() => abort(404));
if (! $image->group?->configs->get(GroupConfigKey::IsEnableOriginalProtection)) {
abort(404);
}
try {
$cacheKey = "image_{$image->key}";
if (Cache::has($cacheKey)) {
$contents = Cache::get($cacheKey);
} else {
$contents = $image->filesystem()->read($image->pathname);
$configs = collect($image->group?->configs->get(GroupConfigKey::WatermarkConfigs));
// 是否启用了水印功能跳过gif和ico图片
if (
$image->group?->configs->get(GroupConfigKey::IsEnableWatermark) &&
$configs->get('mode', Mode::Overlay) == Mode::Dynamic &&
! in_array($image->extension, ['ico', 'gif', 'svg'])
) {
$quality = $image->group?->configs->get(GroupConfigKey::ImageSaveQuality, 75);
$contents = $service->stickWatermark($contents, $configs)->encode($image->extension, $quality)->getEncoded();
}
$cacheTtl = (int)$image->group?->configs->get(GroupConfigKey::ImageCacheTtl, 0);
// 是否启用了缓存
if ($cacheTtl) {
Cache::remember($cacheKey, $cacheTtl, fn () => $contents);
} else {
if (Cache::has($cacheKey)) Cache::forget($cacheKey);
}
}
} catch (FilesystemException $e) {
Utils::e($e, '图片输出时出现异常');
abort(404);
}
$mimetype = $image->mimetype;
// ico svg 图片直接输出,不经过 InterventionImage 处理
if (in_array($image->extension, ['ico', 'svg'])) {
goto out;
}
// 浏览器无法预览的图片,改为 png 格式输出
if (in_array($image->extension, ['psd', 'tif', 'bmp'])) {
$mimetype = 'image/png';
$contents = InterventionImage::make($contents)->encode('png')->getEncoded();
}
out:
return \response()->stream(function () use ($contents) {
echo $contents;
}, headers: ['Content-type' => $mimetype]);
}
}

View File

@@ -0,0 +1,69 @@
<?php
namespace App\Http\Controllers\User;
use App\Http\Controllers\Controller;
use App\Http\Requests\AlbumRequest;
use App\Models\Album;
use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
class AlbumController extends Controller
{
public function albums(): Response
{
/** @var User $user */
$user = Auth::user();
$albums = $user->albums()->latest()->paginate(40);
$albums->getCollection()->each(function (Album $album) {
$album->setVisible(['id', 'name', 'intro', 'image_num']);
});
return $this->success('success', compact('albums'));
}
public function create(AlbumRequest $request): Response
{
/** @var User $user */
$user = Auth::user();
DB::transaction(function () use ($user, $request) {
$user->albums()->create(array_filter($request->validated()));
$user->album_num = $user->albums()->count();
$user->save();
});
return $this->success('创建成功');
}
public function update(AlbumRequest $request): Response
{
/** @var User $user */
$user = Auth::user();
$album = $user->albums()->find($request->route('id'));
if (is_null($album)) {
return $this->fail('不存在的相册');
}
$album->update(array_filter($request->validated()));
return $this->success('修改成功');
}
public function delete(Request $request): Response
{
/** @var User $user */
$user = Auth::user();
/** @var Album|null $album */
$album = $user->albums()->find($request->route('id'));
if (is_null($album)) {
return $this->fail('不存在的相册');
}
DB::transaction(function () use ($user, $album) {
$album->images()->update(['album_id' => null]);
$album->delete();
$user->album_num = $user->albums()->count();
$user->save();
});
return $this->success('删除成功');
}
}

View File

@@ -0,0 +1,121 @@
<?php
namespace App\Http\Controllers\User;
use App\Enums\ImagePermission;
use App\Http\Controllers\Controller;
use App\Http\Requests\ImageRenameRequest;
use App\Models\Album;
use App\Models\Image;
use App\Models\User;
use App\Services\UserService;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
use Illuminate\View\View;
class ImageController extends Controller
{
public function index(): View
{
return view('user.images');
}
public function images(Request $request): Response
{
/** @var User $user */
$user = Auth::user();
$images = $user->images()->filter($request)->with('group', 'strategy')->paginate(40);
$images->getCollection()->each(function (Image $image) {
// 图片宽高过小会导致前端排版异常
$image->width = max($image->width, 200);
$image->height = max($image->height, 200);
$image->human_date = $image->created_at->diffForHumans();
$image->date = $image->created_at->format('Y-m-d H:i:s');
$image->append(['url', 'thumb_url', 'filename', 'links'])->setVisible([
'id', 'filename', 'url', 'thumb_url', 'human_date', 'date', 'size', 'width', 'height', 'links'
]);
});
return $this->success('success', compact('images'));
}
public function image(Request $request): Response
{
/** @var User $user */
$user = Auth::user();
/** @var Image $image */
if (!$image = $user->images()->find($request->route('id'))) {
return $this->fail('未找到该图片');
}
$image->strategy?->setVisible(['name']);
$image->album?->setVisible(['name']);
$image->append(['url', 'thumb_url', 'filename', 'links'])->setVisible([
'id', 'filename', 'origin_name', 'url', 'thumb_url', 'width', 'height', 'size', 'mimetype', 'md5', 'sha1',
'permission', 'strategy', 'album', 'uploaded_ip', 'links', 'created_at'
]);
return $this->success('success', compact('image'));
}
public function permission(Request $request): Response
{
/** @var User $user */
$user = Auth::user();
$permission = $request->input('permission');
$permissions = ['public' => ImagePermission::Public, 'private' => ImagePermission::Private];
if (!in_array($permission, array_keys($permissions))) {
return $this->fail('设置失败');
}
$user->images()->whereIn('id', (array) $request->input('ids'))->update([
'permission' => $permissions[$permission],
]);
return $this->success('设置成功');
}
public function rename(ImageRenameRequest $request): Response
{
/** @var User $user */
$user = Auth::user();
/** @var Image $image */
if ($image = $user->images()->find($request->input('id'))) {
$image->alias_name = $request->input('name');
$image->save();
}
return $this->success('重命名成功', $image->only('id', 'filename'));
}
public function movement(Request $request): Response
{
/** @var User $user */
$user = Auth::user();
DB::transaction(function () use ($user, $request) {
/** @var null|Album $album */
$album = $user->albums()->find((int) $request->input('id'));
$user->images()->whereIn('id', $request->input('selected'))->update([
'album_id' => $album->id ?? null,
]);
if ($album) {
$album->image_num = $album->images()->count();
$album->save();
}
if ($albumId = (int) $request->input('album_id')) {
/** @var Album $originAlbum */
$originAlbum = $user->albums()->find($albumId);
$originAlbum->image_num = $originAlbum->images()->count();
$originAlbum->save();
}
});
return $this->success('移动成功');
}
public function delete(Request $request): Response
{
/** @var User $user */
$user = Auth::user();
(new UserService())->deleteImages($request->all() ?: [], $user);
return $this->success('删除成功');
}
}

View File

@@ -0,0 +1,64 @@
<?php
namespace App\Http\Controllers\User;
use App\Enums\UserConfigKey;
use App\Http\Controllers\Controller;
use App\Http\Requests\UserSettingRequest;
use App\Models\User;
use Illuminate\Auth\Events\PasswordReset;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Str;
use Illuminate\View\View;
class UserController extends Controller
{
public function dashboard(): View
{
/** @var User $user */
$user = Auth::user();
$configs = $user->group->configs;
$strategies = $user->group->strategies()->get();
return view('user.dashboard', compact('strategies', 'configs', 'user'));
}
public function settings(): View
{
return view('user.settings');
}
public function update(UserSettingRequest $request): Response
{
/** @var User $user */
$user = Auth::user();
$user->name = $request->validated('name');
$user->url = $request->validated('url') ?: '';
$user->configs = $user->configs->merge(collect($request->validated('configs'))->transform(function ($value) {
return (int)$value;
}));
if ($password = $request->validated('password')) {
$user->forceFill([
'password' => Hash::make($password),
'remember_token' => Str::random(60),
]);
event(new PasswordReset($user));
}
$user->save();
return $this->success('保存成功');
}
public function setStrategy(Request $request): Response
{
/** @var User $user */
$user = Auth::user();
if (! $strategy = $user->group->strategies()->find($request->id)) {
return $this->fail('没有找到该策略');
}
$user->update(['configs->'.UserConfigKey::DefaultStrategy => $strategy->id]);
return $this->success('设置成功');
}
}

68
app/Http/Kernel.php Normal file
View File

@@ -0,0 +1,68 @@
<?php
namespace App\Http;
use Illuminate\Foundation\Http\Kernel as HttpKernel;
class Kernel extends HttpKernel
{
/**
* The application's global HTTP middleware stack.
*
* These middleware are run during every request to your application.
*
* @var array<int, class-string|string>
*/
protected $middleware = [
// \App\Http\Middleware\TrustHosts::class,
\App\Http\Middleware\TrustProxies::class,
\Fruitcake\Cors\HandleCors::class,
\App\Http\Middleware\PreventRequestsDuringMaintenance::class,
\Illuminate\Foundation\Http\Middleware\ValidatePostSize::class,
\App\Http\Middleware\TrimStrings::class,
\Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class,
];
/**
* The application's route middleware groups.
*
* @var array<string, array<int, class-string|string>>
*/
protected $middlewareGroups = [
'web' => [
\App\Http\Middleware\EncryptCookies::class,
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
\Illuminate\Session\Middleware\StartSession::class,
// \Illuminate\Session\Middleware\AuthenticateSession::class,
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
\App\Http\Middleware\VerifyCsrfToken::class,
\Illuminate\Routing\Middleware\SubstituteBindings::class,
],
'api' => [
\Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class,
'throttle:api',
\Illuminate\Routing\Middleware\SubstituteBindings::class,
],
];
/**
* The application's route middleware.
*
* These middleware may be assigned to groups or used individually.
*
* @var array<string, class-string|string>
*/
protected $routeMiddleware = [
'auth' => \App\Http\Middleware\Authenticate::class,
'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
'auth.admin' => \App\Http\Middleware\AuthenticateWithAdmin::class,
'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class,
'can' => \Illuminate\Auth\Middleware\Authorize::class,
'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
'password.confirm' => \Illuminate\Auth\Middleware\RequirePassword::class,
'signed' => \Illuminate\Routing\Middleware\ValidateSignature::class,
'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class,
];
}

View File

@@ -0,0 +1,21 @@
<?php
namespace App\Http\Middleware;
use Illuminate\Auth\Middleware\Authenticate as Middleware;
class Authenticate extends Middleware
{
/**
* Get the path the user should be redirected to when they are not authenticated.
*
* @param \Illuminate\Http\Request $request
* @return string|null
*/
protected function redirectTo($request)
{
if (! $request->expectsJson()) {
return route('login');
}
}
}

View File

@@ -0,0 +1,24 @@
<?php
namespace App\Http\Middleware;
use App\Models\User;
use Closure;
use Illuminate\Support\Facades\Auth;
class AuthenticateWithAdmin extends Authenticate
{
public function handle($request, Closure $next, ...$guards)
{
$this->authenticate($request, $guards);
/** @var User $user */
$user = Auth::user();
if (! $user->is_adminer) {
return abort(403);
}
return $next($request);
}
}

View File

@@ -0,0 +1,26 @@
<?php
namespace App\Http\Middleware;
use App\Enums\ConfigKey;
use App\Http\Result;
use App\Utils;
use Closure;
use Illuminate\Http\Request;
class CheckIsEnableApi
{
use Result;
public function handle(Request $request, Closure $next)
{
if (! Utils::config(ConfigKey::IsEnableApi)) {
if ($request->expectsJson()) {
return $this->fail('管理员未启用 API')->setStatusCode(403);
}
abort(404);
}
return $next($request);
}
}

View File

@@ -0,0 +1,26 @@
<?php
namespace App\Http\Middleware;
use App\Enums\ConfigKey;
use App\Http\Result;
use App\Utils;
use Closure;
use Illuminate\Http\Request;
class CheckIsEnableGallery
{
use Result;
public function handle(Request $request, Closure $next)
{
if (! Utils::config(ConfigKey::IsEnableGallery)) {
if ($request->expectsJson()) {
return $this->fail('管理员未启用画廊功能')->setStatusCode(403);
}
abort(404);
}
return $next($request);
}
}

View File

@@ -0,0 +1,25 @@
<?php
namespace App\Http\Middleware;
use App\Enums\ConfigKey;
use App\Http\Result;
use App\Utils;
use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
class CheckIsEnableGuestUpload
{
use Result;
public function handle(Request $request, Closure $next)
{
// 检测是否启用了游客上传,未启用、未登录情况下跳转至登录页面
if (! Utils::config(ConfigKey::IsAllowGuestUpload) && Auth::guest()) {
return redirect('login');
}
return $next($request);
}
}

View File

@@ -0,0 +1,26 @@
<?php
namespace App\Http\Middleware;
use App\Enums\ConfigKey;
use App\Http\Result;
use App\Utils;
use Closure;
use Illuminate\Http\Request;
class CheckIsEnableRegistration
{
use Result;
public function handle(Request $request, Closure $next)
{
if (! Utils::config(ConfigKey::IsEnableRegistration)) {
if ($request->expectsJson()) {
return $this->fail('站点管理员关闭了注册功能')->setStatusCode(403);
}
abort(404);
}
return $next($request);
}
}

View File

@@ -0,0 +1,26 @@
<?php
namespace App\Http\Middleware;
use App\Http\Result;
use Closure;
use Illuminate\Http\Request;
class CheckIsInstalled
{
use Result;
public function handle(Request $request, Closure $next)
{
// 检测程序是否安装
if (! file_exists(base_path('installed.lock'))) {
if (! $request->expectsJson()) {
return redirect('install');
} else {
return $this->fail('It has already been installed.');
}
}
return $next($request);
}
}

View File

@@ -0,0 +1,17 @@
<?php
namespace App\Http\Middleware;
use Illuminate\Cookie\Middleware\EncryptCookies as Middleware;
class EncryptCookies extends Middleware
{
/**
* The names of the cookies that should not be encrypted.
*
* @var array<int, string>
*/
protected $except = [
//
];
}

View File

@@ -0,0 +1,17 @@
<?php
namespace App\Http\Middleware;
use Illuminate\Foundation\Http\Middleware\PreventRequestsDuringMaintenance as Middleware;
class PreventRequestsDuringMaintenance extends Middleware
{
/**
* The URIs that should be reachable while maintenance mode is enabled.
*
* @var array<int, string>
*/
protected $except = [
//
];
}

View File

@@ -0,0 +1,32 @@
<?php
namespace App\Http\Middleware;
use App\Providers\RouteServiceProvider;
use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
class RedirectIfAuthenticated
{
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure(\Illuminate\Http\Request): (\Illuminate\Http\Response|\Illuminate\Http\RedirectResponse) $next
* @param string|null ...$guards
* @return \Illuminate\Http\Response|\Illuminate\Http\RedirectResponse
*/
public function handle(Request $request, Closure $next, ...$guards)
{
$guards = empty($guards) ? [null] : $guards;
foreach ($guards as $guard) {
if (Auth::guard($guard)->check()) {
return redirect(RouteServiceProvider::HOME);
}
}
return $next($request);
}
}

View File

@@ -0,0 +1,19 @@
<?php
namespace App\Http\Middleware;
use Illuminate\Foundation\Http\Middleware\TrimStrings as Middleware;
class TrimStrings extends Middleware
{
/**
* The names of the attributes that should not be trimmed.
*
* @var array<int, string>
*/
protected $except = [
'current_password',
'password',
'password_confirmation',
];
}

View File

@@ -0,0 +1,20 @@
<?php
namespace App\Http\Middleware;
use Illuminate\Http\Middleware\TrustHosts as Middleware;
class TrustHosts extends Middleware
{
/**
* Get the host patterns that should be trusted.
*
* @return array<int, string|null>
*/
public function hosts()
{
return [
$this->allSubdomainsOfApplicationUrl(),
];
}
}

View File

@@ -0,0 +1,28 @@
<?php
namespace App\Http\Middleware;
use Illuminate\Http\Middleware\TrustProxies as Middleware;
use Illuminate\Http\Request;
class TrustProxies extends Middleware
{
/**
* The trusted proxies for this application.
*
* @var array<int, string>|string|null
*/
protected $proxies = '*';
/**
* The headers that should be used to detect proxies.
*
* @var int
*/
protected $headers =
Request::HEADER_X_FORWARDED_FOR |
Request::HEADER_X_FORWARDED_HOST |
Request::HEADER_X_FORWARDED_PORT |
Request::HEADER_X_FORWARDED_PROTO |
Request::HEADER_X_FORWARDED_AWS_ELB;
}

View File

@@ -0,0 +1,17 @@
<?php
namespace App\Http\Middleware;
use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken as Middleware;
class VerifyCsrfToken extends Middleware
{
/**
* The URIs that should be excluded from CSRF verification.
*
* @var array<int, string>
*/
protected $except = [
//
];
}

View File

@@ -0,0 +1,165 @@
<?php
namespace App\Http\Requests\Admin;
use App\Http\Requests\FormRequest;
use Illuminate\Validation\Rule;
class GroupRequest extends FormRequest
{
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
$requiredIfReview = function ($driver) {
return Rule::requiredIf($this->input('configs.is_enable_scan') && $this->input('configs.scan_configs.driver') === $driver);
};
$requiredIfWatermark = function ($driver) {
return Rule::requiredIf($this->input('configs.is_enable_watermark') && $this->input('configs.watermark_configs.driver') === $driver);
};
return [
'name' => 'required|between:2,30',
'is_default' => 'boolean',
'is_guest' => 'boolean',
'configs' => 'required|array',
'configs.maximum_file_size' => 'required|numeric',
'configs.concurrent_upload_num' => 'required|integer',
'configs.limit_per_minute' => 'required|integer',
'configs.limit_per_hour' => 'required|integer',
'configs.limit_per_day' => 'required|integer',
'configs.limit_per_week' => 'required|integer',
'configs.limit_per_month' => 'required|integer',
'configs.image_save_quality' => 'required|min:1|max:100',
'configs.image_save_format' => '',
'configs.path_naming_rule' => 'max:400',
'configs.file_naming_rule' => 'max:400',
'configs.accepted_file_suffixes' => 'required|array|in:jpeg,jpg,png,gif,tif,bmp,ico,psd,webp,svg',
'configs.is_enable_scan' => 'boolean',
'configs.scanned_action' => [
'exclude_if:configs.is_enable_scan,false',
'in:mark,delete',
],
'configs.scan_configs.driver' => ['exclude_if:configs.is_enable_scan,false', 'in:tencent,aliyun,nsfwjs'],
'configs.scan_configs.drivers.tencent.endpoint' => [$requiredIfReview('tencent')],
'configs.scan_configs.drivers.tencent.secret_id' => [$requiredIfReview('tencent')],
'configs.scan_configs.drivers.tencent.secret_key' => [$requiredIfReview('tencent')],
'configs.scan_configs.drivers.tencent.region' => [$requiredIfReview('tencent')],
'configs.scan_configs.drivers.tencent.biz_type' => '',
'configs.scan_configs.drivers.aliyun.access_key_id' => [$requiredIfReview('aliyun')],
'configs.scan_configs.drivers.aliyun.access_key_secret' => [$requiredIfReview('aliyun')],
'configs.scan_configs.drivers.aliyun.region_id' => [$requiredIfReview('aliyun')],
'configs.scan_configs.drivers.aliyun.biz_type' => '',
'configs.scan_configs.drivers.aliyun.scenes' => [$requiredIfReview('aliyun'), 'array'],
'configs.scan_configs.drivers.nsfwjs.api_url' => [$requiredIfReview('nsfwjs')],
'configs.scan_configs.drivers.nsfwjs.attr_name' => [$requiredIfReview('nsfwjs'), 'nullable'],
'configs.scan_configs.drivers.nsfwjs.threshold' => [$requiredIfReview('nsfwjs'), 'nullable', 'integer', 'between:1,100'],
'configs.is_enable_original_protection' => 'boolean',
'configs.image_cache_ttl' => 'nullable|numeric',
'configs.is_enable_watermark' => 'boolean',
'configs.watermark_configs.mode' => ['in:1,2'],
'configs.watermark_configs.driver' => ['exclude_if:configs.is_enable_watermark,false', 'in:font,image'],
'configs.watermark_configs.drivers.font.font' => [
$requiredIfWatermark('font'),
function ($attribute, $value, $fail) {
if (! file_exists(storage_path('app/public/'.$value))) {
$fail('字体文件不存在');
}
},
],
'configs.watermark_configs.drivers.font.position' => [$requiredIfWatermark('font')],
'configs.watermark_configs.drivers.font.text' => [$requiredIfWatermark('font')],
'configs.watermark_configs.drivers.font.color' => [$requiredIfWatermark('font')],
'configs.watermark_configs.drivers.font.size' => [$requiredIfWatermark('font'), 'nullable', 'integer'],
'configs.watermark_configs.drivers.font.angle' => [$requiredIfWatermark('font'), 'nullable', 'integer'],
'configs.watermark_configs.drivers.font.x' => [$requiredIfWatermark('font'), 'nullable', 'integer'],
'configs.watermark_configs.drivers.font.y' => [$requiredIfWatermark('font'), 'nullable', 'integer'],
'configs.watermark_configs.drivers.image.image' => [
$requiredIfWatermark('image'),
function ($attribute, $value, $fail) {
if (! file_exists(storage_path('app/public/'.$value))) {
$fail('图片文件不存在');
}
},
],
'configs.watermark_configs.drivers.image.position' => [$requiredIfWatermark('image')],
'configs.watermark_configs.drivers.image.width' => [$requiredIfWatermark('image'), 'nullable', 'integer'],
'configs.watermark_configs.drivers.image.height' => [$requiredIfWatermark('image'), 'nullable', 'integer'],
'configs.watermark_configs.drivers.image.opacity' => [
$requiredIfWatermark('image'), 'nullable', 'integer', 'between:0,100',
],
'configs.watermark_configs.drivers.image.rotate' => [$requiredIfWatermark('image'), 'nullable', 'integer'],
'configs.watermark_configs.drivers.image.x' => [$requiredIfWatermark('image'), 'nullable', 'integer'],
'configs.watermark_configs.drivers.image.y' => [$requiredIfWatermark('image'), 'nullable', 'integer'],
];
}
public function attributes()
{
return [
'name' => '组名称',
'is_default' => '是否默认',
'configs' => '组配置',
'configs.maximum_file_size' => '文件最大上传大小',
'configs.concurrent_upload_num' => '并发上传数量',
'configs.limit_per_minute' => '每分钟上传限制',
'configs.limit_per_hour' => '每小时上传限制',
'configs.limit_per_day' => '每天上传限制',
'configs.limit_per_week' => '每周上传限制',
'configs.limit_per_month' => '每月上传限制',
'configs.path_naming_rule' => '路径命名规则',
'configs.file_naming_rule' => '文件命名规则',
'configs.image_save_quality' => '图片保存质量',
'configs.image_save_format' => '图片保存格式',
'configs.accepted_file_suffixes' => '允许上传的文件后缀',
'configs.is_enable_scan' => '是否启用图片审核',
'configs.scanned_action' => '图片审核动作',
'configs.scan_configs.driver' => '图片审核驱动',
'configs.scan_configs.drivers.tencent.endpoint' => 'Endpoint',
'configs.scan_configs.drivers.tencent.secret_id' => 'SecretId',
'configs.scan_configs.drivers.tencent.secret_key' => 'SecretKey',
'configs.scan_configs.drivers.tencent.region' => '地域节点',
'configs.scan_configs.drivers.tencent.biz_type' => '业务场景',
'configs.scan_configs.drivers.aliyun.access_key_id' => 'AccessKeyId',
'configs.scan_configs.drivers.aliyun.access_key_secret' => 'AccessKeySecret',
'configs.scan_configs.drivers.aliyun.region_id' => '地域节点',
'configs.scan_configs.drivers.aliyun.biz_type' => '场景名称',
'configs.scan_configs.drivers.aliyun.scenes' => '审核场景',
'configs.scan_configs.drivers.nsfwjs.api_url' => '接口地址',
'configs.scan_configs.drivers.nsfwjs.attr_name' => '表单名称',
'configs.scan_configs.drivers.nsfwjs.threshold' => '阈值',
'configs.is_enable_original_protection' => '是否启用原图保护功能',
'configs.image_cache_ttl' => '图片缓存时间',
'configs.is_enable_watermark' => '是否启用水印功能',
'configs.watermark_configs.driver' => '水印驱动',
'configs.watermark_configs.drivers.font.font' => '字体文件',
'configs.watermark_configs.drivers.font.position' => '水印位置',
'configs.watermark_configs.drivers.font.text' => '水印文字',
'configs.watermark_configs.drivers.font.color' => '字体颜色',
'configs.watermark_configs.drivers.font.size' => '水印文字大小',
'configs.watermark_configs.drivers.font.angle' => '水印旋转角度',
'configs.watermark_configs.drivers.font.x' => '水印X轴偏移量',
'configs.watermark_configs.drivers.font.y' => '水印Y轴偏移量',
'configs.watermark_configs.drivers.image.image' => '水印图片文件',
'configs.watermark_configs.drivers.image.position' => '水印位置',
'configs.watermark_configs.drivers.image.width' => '水印图片宽度',
'configs.watermark_configs.drivers.image.height' => '水印图片高度',
'configs.watermark_configs.drivers.image.opacity' => '水印透明度',
'configs.watermark_configs.drivers.image.rotate' => '水印旋转角度',
'configs.watermark_configs.drivers.image.x' => '水印X轴偏移量',
'configs.watermark_configs.drivers.image.y' => '水印Y轴偏移量',
];
}
}

View File

@@ -0,0 +1,221 @@
<?php
namespace App\Http\Requests\Admin;
use App\Enums\StrategyKey;
use App\Http\Requests\FormRequest;
use App\Models\Strategy;
use Illuminate\Support\Collection;
class StrategyRequest extends FormRequest
{
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
$checkUrl = function ($attribute, $value, $fail) {
if ($this->input('key') == StrategyKey::Local) {
$folders = [config('app.thumbnail_path'), 'fonts', 'css', 'js'];
$symlink = Strategy::getRootPath($value);
if (! $symlink) {
return $fail('访问域名缺少根路径');
}
if (in_array($symlink, $folders)) {
return $fail('系统保留路径:'. $symlink);
}
if (false !== strpbrk($symlink, "\\/?%*:|\"<>")) {
return $fail('根路径名称不符合规则');
}
// 修改时单独判断
if ($this->method() === 'PUT') {
$symlinks = Strategy::query()
->where('id', '<>', $this->route('id'))
->pluck('configs')
->transform(function (Collection $configs) {
return Strategy::getRootPath($configs->get('url'));
})->merge($folders)->values()->toArray();
if (in_array($symlink, $symlinks)) {
return $fail('根路径已经存在');
}
} else {
if (realpath(public_path($symlink))) {
return $fail('根路径已经存在');
}
}
}
};
$array = [
'groups' => 'array',
'name' => 'required|max:60',
'intro' => 'max:2000',
'key' => 'required|integer',
'configs.url' => ['required', 'url'],
'configs.queries' => '',
];
return array_merge($array, match((int)$this->input('key')) {
StrategyKey::Local => [
'configs.url' => ['required', 'url', $checkUrl],
'configs.root' => ['max:1000', function ($attribute, $value, $fail) {
if ($value) {
if (! is_dir($value)) {
return $fail('储存路径不存在');
}
if (! is_writeable($value)) {
return $fail('储存路径没有写入权限');
}
}
}],
],
StrategyKey::S3 => [
'configs.access_key_id' => 'required',
'configs.secret_access_key' => 'required',
'configs.endpoint' => '',
'configs.region' => '',
'configs.bucket' => 'required',
],
StrategyKey::Oss => [
'configs.access_key_id' => 'required',
'configs.access_key_secret' => 'required',
'configs.endpoint' => 'required',
'configs.bucket' => 'required',
],
StrategyKey::Cos => [
'configs.app_id' => 'required',
'configs.secret_id' => 'required',
'configs.secret_key' => 'required',
'configs.region' => 'required',
'configs.bucket' => 'required',
],
StrategyKey::Kodo => [
'configs.access_key' => 'required',
'configs.secret_key' => 'required',
'configs.bucket' => 'required',
],
StrategyKey::Uss => [
'configs.service' => 'required',
'configs.operator' => 'required',
'configs.password' => 'required',
],
StrategyKey::Sftp => [
'configs.root' => 'required',
'configs.host' => 'required',
'configs.port' => 'integer',
'configs.username' => 'required',
'configs.password' => 'required_if:configs.private_key,null',
'configs.private_key' => 'required_if:configs.password,null',
'configs.passphrase' => '',
'configs.use_agent' => 'required|boolean',
],
StrategyKey::Ftp => [
'configs.root' => 'required',
'configs.host' => 'required',
'configs.port' => 'integer',
'configs.username' => 'required',
'configs.password' => 'required',
'configs.ssl' => 'required|boolean',
'configs.passive' => 'required|boolean',
],
StrategyKey::Webdav => [
'configs.base_uri' => 'required',
'configs.username' => '',
'configs.password' => '',
'configs.auth_type' => '',
'configs.prefix' => '',
],
StrategyKey::Minio => [
'configs.access_key' => 'required',
'configs.secret_key' => 'required',
'configs.endpoint' => '',
'configs.region' => '',
'configs.bucket' => 'required',
'configs.bucket_endpoint' => '',
],
});
}
public function attributes()
{
$array = [
'name' => '名称',
'intro' => '简介',
'key' => '策略',
'configs.url' => '访问网址',
'configs.queries' => 'Url 额外参数',
];
return array_merge($array, match((int)$this->input('key')) {
StrategyKey::Local => [
'configs.root' => '根目录路径',
],
StrategyKey::S3 => [
'configs.access_key_id' => 'AccessKeyId',
'configs.secret_access_key' => 'SecretAccessKey',
'configs.endpoint' => '连接地址',
'configs.region' => '区域',
'configs.bucket' => '储存桶名称',
],
StrategyKey::Oss => [
'configs.access_key_id' => 'AccessKeyId',
'configs.access_key_secret' => 'AccessKeySecret',
'configs.endpoint' => '地域节点域名',
'configs.bucket' => 'Bucket 名称',
],
StrategyKey::Cos => [
'configs.app_id' => 'AppId',
'configs.secret_id' => 'SecretId',
'configs.secret_key' => 'SecretKey',
'configs.region' => '所属地域',
'configs.bucket' => '储存桶名称',
],
StrategyKey::Kodo => [
'configs.access_key' => 'AccessKey',
'configs.secret_key' => 'SecretKey',
'configs.bucket' => 'Bucket',
],
StrategyKey::Uss => [
'configs.service' => '服务名称',
'configs.operator' => '操作员名称',
'configs.password' => '操作员密码',
],
StrategyKey::Sftp => [
'configs.root' => '根目录路径',
'configs.host' => '主机地址',
'configs.port' => '连接端口',
'configs.username' => '用户名',
'configs.password' => '密码',
'configs.private_key' => '密钥',
'configs.passphrase' => '密钥口令',
'configs.use_agent' => '使用代理',
],
StrategyKey::Ftp => [
'configs.root' => '根目录路径',
'configs.host' => '主机地址',
'configs.port' => '连接端口',
'configs.username' => '用户名',
'configs.password' => '密码',
'configs.ssl' => '加密连接',
'configs.passive' => '被动模式',
],
StrategyKey::Webdav => [
'configs.base_uri' => '连接地址',
'configs.username' => '用户名',
'configs.password' => '密码',
'configs.auth_type' => '认证方式',
'configs.prefix' => '前缀',
],
StrategyKey::Minio => [
'configs.access_key' => 'AccessKey',
'configs.secret_key' => 'SecretKey',
'configs.endpoint' => '连接地址',
'configs.region' => '区域',
'configs.bucket' => 'Bucket 名称',
'configs.bucket_endpoint' => 'BucketEndpoint',
],
});
}
}

View File

@@ -0,0 +1,36 @@
<?php
namespace App\Http\Requests\Admin;
use App\Http\Requests\FormRequest;
use Illuminate\Validation\Rules;
class UserRequest extends FormRequest
{
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
'group_id' => 'required',
'name' => 'required|between:2,30',
'capacity' => 'required|numeric',
'password' => ['nullable', Rules\Password::defaults()],
'status' => 'in:1,0'
];
}
public function attributes()
{
return [
'group_id' => '角色组',
'name' => '昵称',
'capacity' => '总容量',
'password' => '密码',
'status' => '状态',
];
}
}

View File

@@ -0,0 +1,29 @@
<?php
namespace App\Http\Requests;
class AlbumRequest extends FormRequest
{
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
'name' => 'required|max:60|alpha_dash',
'intro' => 'max:600'
];
}
public function messages()
{
return [
'name.required' => '名称不能为空',
'name.max' => '名称字符过长,最大不能超过 60',
'name.alpha_dash' => '名称只能是字母、数字,短破折号(-和下划线_',
'intro.max' => '简介字符过长,最大不能超过 600'
];
}
}

View File

@@ -0,0 +1,93 @@
<?php
namespace App\Http\Requests\Auth;
use Illuminate\Auth\Events\Lockout;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\RateLimiter;
use Illuminate\Support\Str;
use Illuminate\Validation\ValidationException;
class LoginRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
'email' => ['required', 'string', 'email'],
'password' => ['required', 'string'],
];
}
/**
* Attempt to authenticate the request's credentials.
*
* @return void
*
* @throws \Illuminate\Validation\ValidationException
*/
public function authenticate()
{
$this->ensureIsNotRateLimited();
if (! Auth::attempt($this->only('email', 'password'), $this->boolean('remember'))) {
RateLimiter::hit($this->throttleKey());
throw ValidationException::withMessages([
'email' => __('auth.failed'),
]);
}
RateLimiter::clear($this->throttleKey());
}
/**
* Ensure the login request is not rate limited.
*
* @return void
*
* @throws \Illuminate\Validation\ValidationException
*/
public function ensureIsNotRateLimited()
{
if (! RateLimiter::tooManyAttempts($this->throttleKey(), 5)) {
return;
}
event(new Lockout($this));
$seconds = RateLimiter::availableIn($this->throttleKey());
throw ValidationException::withMessages([
'email' => trans('auth.throttle', [
'seconds' => $seconds,
'minutes' => ceil($seconds / 60),
]),
]);
}
/**
* Get the rate limiting throttle key for the request.
*
* @return string
*/
public function throttleKey()
{
return Str::lower($this->input('email')).'|'.$this->ip();
}
}

View File

@@ -0,0 +1,22 @@
<?php
namespace App\Http\Requests;
use App\Http\Result;
use Illuminate\Contracts\Validation\Validator;
use Illuminate\Http\Exceptions\HttpResponseException;
class FormRequest extends \Illuminate\Foundation\Http\FormRequest
{
use Result;
public function authorize()
{
return true;
}
protected function failedValidation(Validator $validator)
{
throw (new HttpResponseException($this->fail($validator->errors()->first())));
}
}

View File

@@ -0,0 +1,40 @@
<?php
namespace App\Http\Requests;
class ImageRenameRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
'id' => 'required|numeric',
'name' => 'required|max:50|string',
];
}
public function messages()
{
return [
'id.required' => '请选择一张图片',
'id.numeric' => '图片选择异常',
'name.required' => '请输入名称',
'name.max' => '名称长度不能超过 50 个字符',
'name.string' => '名称格式不正确',
];
}
}

View File

@@ -0,0 +1,46 @@
<?php
namespace App\Http\Requests;
class UserSettingRequest extends FormRequest
{
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
'name' => 'required|between:2,20',
'url' => 'nullable|url',
'password' => 'nullable|between:6,32',
'configs' => 'array',
'configs.default_album' => 'required|numeric',
'configs.default_strategy' => 'required|numeric',
'configs.default_permission' => 'required|in:1,0',
'configs.pasted_action' => 'required|in:1,2',
'configs.is_auto_clear_preview' => 'nullable|boolean'
];
}
public function messages()
{
return [
'name.required' => '昵称不能为空',
'name.between' => '昵称必须在 2-20 个字符之间',
'url.url' => '个人主页地址格式不正确',
'password.between' => '密码必须在 6-32 个字符之间',
'configs.array' => '配置值不正确',
'configs.default_album.required' => '默认相册选择错误',
'configs.default_album.numeric' => '默认相册选择错误',
'configs.default_strategy.required' => '默认策略选择错误',
'configs.default_strategy.numeric' => '默认策略选择错误',
'configs.default_permission.required' => '权限值选择错误',
'configs.default_permission.in' => '权限值不正确',
'configs.pasted_action.required' => '粘贴动作值选择错误',
'configs.pasted_action.in' => '粘贴动作值不正确',
'configs.is_auto_clear_preview.boolean' => '是否自动清除预览选择错误'
];
}
}

24
app/Http/Result.php Normal file
View File

@@ -0,0 +1,24 @@
<?php
namespace App\Http;
use Illuminate\Http\Response;
trait Result
{
public function success(string $message = 'success', $data = []): Response
{
return $this->response(true, $message, $data);
}
public function fail(string $message = 'fail', $data = []): Response
{
return $this->response(false, $message, $data);
}
public function response(bool $status, string $message = '', $data = []): Response
{
$data = $data ?: new \stdClass;
return response(compact('status', 'message', 'data'));
}
}

33
app/Mail/Test.php Normal file
View File

@@ -0,0 +1,33 @@
<?php
namespace App\Mail;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Mail\Mailable;
use Illuminate\Queue\SerializesModels;
class Test extends Mailable
{
use Queueable, SerializesModels;
/**
* Create a new message instance.
*
* @return void
*/
public function __construct()
{
//
}
/**
* Build the message.
*
* @return $this
*/
public function build()
{
return $this->view('emails.test');
}
}

75
app/Models/Album.php Normal file
View File

@@ -0,0 +1,75 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Http\Request;
/**
* @property int $id
* @property int $user_id
* @property string $name
* @property string $intro
* @property int $image_num
* @property-read User $user
* @property-read Collection $images
*/
class Album extends Model
{
use HasFactory;
protected $fillable = [
'name',
'intro',
'image_num'
];
protected $hidden = [
'user_id',
];
protected $attributes = [
'intro' => '',
];
protected $casts = [
'id' => 'integer',
'user_id' => 'integer',
'image_num' => 'integer',
];
public function scopeFilter(Builder $builder, Request $request)
{
return $builder->when($request->query('order') ?: 'newest', function (Builder $builder, $order) {
switch ($order) {
case 'earliest':
$builder->orderBy('created_at');
break;
case 'most':
$builder->orderByDesc('image_num');
break;
case 'least':
$builder->orderBy('image_num');
break;
default:
$builder->latest();
}
})->when($request->query('keyword'), function (Builder $builder, $keyword) {
$builder->where('name', 'like', "%{$keyword}%")->orWhere('intro', 'like', "%{$keyword}%");
});
}
public function user(): BelongsTo
{
return $this->belongsTo(User::class, 'user_id', 'id');
}
public function images(): HasMany
{
return $this->hasMany(Image::class, 'album_id', 'id');
}
}

18
app/Models/Config.php Normal file
View File

@@ -0,0 +1,18 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
/**
* @property string $name
* @property mixed $value
*/
class Config extends Model
{
use HasFactory;
public $incrementing = false;
protected $keyType = 'string';
}

101
app/Models/Group.php Normal file
View File

@@ -0,0 +1,101 @@
<?php
namespace App\Models;
use App\Utils;
use Illuminate\Support\Carbon;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Support\Collection;
/**
* @property int $id
* @property string $name
* @property boolean $is_default
* @property boolean $is_guest
* @property Collection $configs
* @property Carbon $updated_at
* @property Carbon $created_at
* @property-read \Illuminate\Database\Eloquent\Collection $users
* @property-read \Illuminate\Database\Eloquent\Collection $images
* @property-read \Illuminate\Database\Eloquent\Collection $strategies
*/
class Group extends Model
{
use HasFactory;
protected $fillable = [
'name',
'is_default',
'is_guest',
'configs',
];
protected $casts = [
'id' => 'integer',
'is_default' => 'bool',
'is_guest' => 'bool',
'configs' => 'collection'
];
const POSITIONS = [
'top-left' => '左上角',
'top' => '上中',
'top-right' => '右上角',
'left' => '左边',
'center' => '中间',
'right' => '右边',
'bottom-left' => '左下角',
'bottom' => '下中',
'bottom-right' => '右下角',
'tiled' => '平铺',
];
const SCENES = [
'porn' => '智能鉴黄',
'terrorism' => '暴恐涉政',
'ad' => '图文违规',
'qrcode' => '二维码',
'live' => '不良场景',
'logo' => 'Logo',
];
protected static function booted()
{
static::saving(function (self $group) {
if ($group->isDirty('is_default') && $group->is_default) {
Group::query()->where('is_default', true)->update(['is_default' => false]);
}
if ($group->isDirty('is_guest') && $group->is_guest) {
Group::query()->where('is_guest', true)->update(['is_guest' => false]);
}
$group->configs = Utils::parseConfigs(self::getDefaultConfigs()->toArray(), $group->configs->toArray());
});
}
/**
* 获取组默认配置
*
* @return Collection
*/
public static function getDefaultConfigs(): Collection
{
return collect(config('convention.group'));
}
public function users(): HasMany
{
return $this->hasMany(User::class, 'group_id', 'id');
}
public function images(): HasMany
{
return $this->hasMany(Image::class, 'group_id', 'id');
}
public function strategies(): BelongsToMany
{
return $this->belongsToMany(Strategy::class, 'group_strategy', 'group_id', 'strategy_id');
}
}

254
app/Models/Image.php Normal file
View File

@@ -0,0 +1,254 @@
<?php
namespace App\Models;
use App\Enums\GroupConfigKey;
use App\Enums\ImagePermission;
use App\Services\ImageService;
use App\Utils;
use Illuminate\Support\Carbon;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Http\Request;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Str;
use League\Flysystem\Filesystem;
/**
* @property int $id
* @property int $user_id
* @property int $album_id
* @property int $group_id
* @property int $strategy_id
* @property string $key
* @property string $path
* @property string $name
* @property string $pathname
* @property string $origin_name
* @property string $alias_name
* @property string $filename
* @property float $size
* @property string $mimetype
* @property string $extension
* @property string $md5
* @property string $sha1
* @property integer $width
* @property integer $height
* @property string $url
* @property string $thumb_url
* @property Collection $links
* @property int $permission
* @property boolean $is_unhealthy
* @property string $uploaded_ip
* @property Carbon $updated_at
* @property Carbon $created_at
* @property-read User $user
* @property-read Album $album
* @property-read Group $group
* @property-read Strategy $strategy
*/
class Image extends Model
{
use HasFactory;
protected $fillable = [
'key',
'path',
'name',
'origin_name',
'alias_name',
'size',
'mimetype',
'extension',
'md5',
'sha1',
'width',
'height',
'permission',
'is_unhealthy',
'uploaded_ip',
];
protected $hidden = [
'user_id',
'album_id',
'group_id',
'strategy_id',
];
protected $casts = [
'id' => 'integer',
'user_id' => 'integer',
'album_id' => 'integer',
'group_id' => 'integer',
'strategy_id' => 'integer',
'width' => 'integer',
'height' => 'integer',
'size' => 'float',
'is_unhealthy' => 'bool',
'permission' => 'integer',
];
protected ?Filesystem $filesystem = null;
protected static function booted()
{
static::creating(function (self $image) {
$image->key = $image->generateKey();
});
static::deleting(function (self $image) {
// TODO 检测是否启用了队列,放置队列中异步删除
// 在当前图片所属的策略中是否存在其他相同 md5 和 sha1 的记录,没有则可以删除物理文件
if (!static::query()
->where('strategy_id', $image->strategy_id)
->where('id', '<>', $image->id)
->where('md5', $image->md5)
->where('sha1', $image->sha1)
->exists()
) {
// 删除本地缓存文件
try {
// 删除物理文件
$image->filesystem()->delete($image->pathname);
@unlink(public_path($image->getThumbnailPathname()));
// 删除缓存
Cache::forget("image_{$image->key}");
} catch (\Throwable $e) {
Utils::e($e, '删除物理文件时发生异常');
}
}
});
}
public function scopeFilter(Builder $builder, Request $request)
{
return $builder->when($request->query('order') ?: 'newest', function (Builder $builder, $order) {
switch ($order) {
case 'earliest':
$builder->orderBy('created_at');
break;
case 'utmost':
$builder->orderByDesc('size');
break;
case 'least':
$builder->orderBy('size');
break;
default:
$builder->latest();
}
})->when($request->query('permission') ?: 'all', function (Builder $builder, $permission) {
switch ($permission) {
case 'public':
$builder->where('permission', ImagePermission::Public);
break;
case 'private':
$builder->where('permission', ImagePermission::Private);
break;
}
})->when($request->query('keyword'), function (Builder $builder, $keyword) {
$builder->where('origin_name', 'like', "%{$keyword}%")->orWhere('alias_name', 'like', "%{$keyword}%");
})->when((int) $request->query('album_id'), function (Builder $builder, $albumId) {
$builder->where('album_id', $albumId);
}, function (Builder $builder) {
$builder->whereNull('album_id');
});
}
public function filename(): Attribute
{
return new Attribute(fn() => $this->alias_name ?: $this->origin_name);
}
public function pathname(): Attribute
{
$path = $this->path ? "{$this->path}/" : '';
return new Attribute(fn() => "{$path}{$this->name}");
}
public function url(): Attribute
{
return new Attribute(function () {
// 是否启用原图保护功能
if ($this->group?->configs->get(GroupConfigKey::IsEnableOriginalProtection)) {
$url = asset("{$this->key}.{$this->extension}");
} else {
$url = rtrim($this->strategy?->configs->get('url'), '/').'/'.ltrim($this->pathname, '/');
}
// 拼接图片 url
return $url.($this->strategy?->configs->get('queries') ?: '');
});
}
public function thumbUrl(): Attribute
{
return new Attribute(function () {
$pathname = $this->getThumbnailPathname();
// 没有缩略图则返回原图
if (! file_exists(public_path($pathname))) {
return $this->url;
}
return asset($pathname);
});
}
public function links(): Attribute
{
return new Attribute(fn() => collect([
'url' => $this->url,
'html' => "&lt;img src=\"{$this->url}\" alt=\"{$this->origin_name}\" title=\"{$this->origin_name}\" /&gt;",
'bbcode' => "[img]{$this->url}[/img]",
'markdown' => "![{$this->origin_name}]({$this->url})",
'markdown_with_link' => "[![{$this->origin_name}]({$this->url})]({$this->url})",
'thumbnail_url' => $this->thumb_url,
]));
}
public function filesystem(): Filesystem
{
if (is_null($this->filesystem)) {
$this->filesystem = new Filesystem((new ImageService())->getAdapter($this->strategy));
}
return $this->filesystem;
}
public function user(): BelongsTo
{
return $this->belongsTo(User::class, 'user_id', 'id');
}
public function album(): BelongsTo
{
return $this->belongsTo(Album::class, 'album_id', 'id');
}
public function group(): BelongsTo
{
return $this->belongsTo(Group::class, 'group_id', 'id');
}
public function strategy(): BelongsTo
{
return $this->belongsTo(Strategy::class, 'strategy_id', 'id');
}
public function getThumbnailPathname(): string
{
return trim(config('app.thumbnail_path'), '/')."/{$this->md5}.". ($this->extension === 'svg' ? 'svg' : "png");
}
private function generateKey($length = 6): string
{
$key = Str::random($length);
if (self::query()->where('key', $key)->exists()) {
return $this->generateKey(++$length);
}
return $key;
}
}

13
app/Models/Model.php Normal file
View File

@@ -0,0 +1,13 @@
<?php
namespace App\Models;
use Carbon\CarbonInterface;
abstract class Model extends \Illuminate\Database\Eloquent\Model
{
protected function serializeDate(\DateTimeInterface $date): string
{
return $date->format(CarbonInterface::DEFAULT_TO_STRING_FORMAT);
}
}

120
app/Models/Strategy.php Normal file
View File

@@ -0,0 +1,120 @@
<?php
namespace App\Models;
use App\Enums\StrategyKey;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Filesystem\Filesystem;
use Illuminate\Support\Carbon;
use Sabre\DAV\Client;
/**
* @property int $id
* @property int $key
* @property string $name
* @property string $intro
* @property \Illuminate\Support\Collection $configs
* @property Carbon $updated_at
* @property Carbon $created_at
* @property-read Collection $groups
* @property-read Collection $images
*/
class Strategy extends Model
{
use HasFactory;
protected $fillable = [
'key',
'name',
'intro',
'configs',
];
protected $attributes = [
'intro' => '',
];
protected $casts = [
'id' => 'integer',
'key' => 'integer',
'configs' => 'collection',
];
const DRIVERS = [
StrategyKey::Local => '本地',
StrategyKey::S3 => 'AWS S3',
StrategyKey::Oss => '阿里云 OSS',
StrategyKey::Cos => '腾讯云 COS',
StrategyKey::Kodo => '七牛云 Kodo',
StrategyKey::Uss => '又拍云 USS',
StrategyKey::Sftp => 'SFTP',
StrategyKey::Ftp => 'FTP',
StrategyKey::Webdav => 'WebDav',
StrategyKey::Minio => 'Minio',
];
const WEBDAV_AUTH_TYPES = [
'' => 'Auto',
Client::AUTH_BASIC => 'Basic',
Client::AUTH_DIGEST => 'Digest',
Client::AUTH_NTLM => 'Ntlm',
];
protected static function booted()
{
static::saving(function (self $strategy) {
$strategy->configs['root'] = $strategy->configs->get('root');
$strategy->configs['url'] = rtrim($strategy->configs->get('url'), '/');
if ($strategy->key == StrategyKey::Local) {
$symlink = self::getRootPath($strategy->configs['url']);
$target = $strategy->configs['root'] ?: config('filesystems.disks.uploads.root');
if (! is_dir(public_path($symlink))) {
(new Filesystem())->link($target, $symlink);
}
// 是否需要移除旧的符号链接
$url = $strategy->getOriginal('configs')['url'] ?? '';
if ($url) {
$oldSymlink = self::getRootPath($url);
if ($oldSymlink != $symlink) {
@unlink($oldSymlink);
}
}
}
});
static::deleted(function (self $strategy) {
// 如果是本地策略,删除的时候同时删除符号连接
if ($strategy->key === StrategyKey::Local) {
$symlink = self::getRootPath($strategy->configs['url']);
@unlink(public_path($symlink));
}
});
}
public static function getRootPath($url): string
{
$path = (parse_url($url)['path'] ?? '');
return current(array_filter(explode('/', $path)));
}
public function intro(): Attribute
{
return new Attribute(
set: fn ($value) => $value ?: '',
);
}
public function groups(): BelongsToMany
{
return $this->belongsToMany(Group::class, 'group_strategy', 'strategy_id', 'group_id');
}
public function images(): HasMany
{
return $this->hasMany(Image::class, 'strategy_id', 'id');
}
}

130
app/Models/User.php Normal file
View File

@@ -0,0 +1,130 @@
<?php
namespace App\Models;
use App\Enums\ConfigKey;
use App\Utils;
use Illuminate\Support\Carbon;
use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Illuminate\Support\Collection;
use Laravel\Sanctum\HasApiTokens;
/**
* @property int $id
* @property int $group_id
* @property string $name
* @property string $email
* @property string $password
* @property string $remember_token
* @property boolean $is_adminer
* @property float $capacity
* @property float $use_capacity
* @property string $url
* @property Collection $configs
* @property int $image_num
* @property int $album_num
* @property string $registered_ip
* @property int $status
* @property Carbon $email_verified_at
* @property Carbon $updated_at
* @property Carbon $created_at
* @property-read string $avatar
* @property-read Group $group
* @property-read \Illuminate\Database\Eloquent\Collection $albums
* @property-read \Illuminate\Database\Eloquent\Collection $images
*/
class User extends Authenticatable implements MustVerifyEmail
{
use HasApiTokens, HasFactory, Notifiable;
/**
* The attributes that are mass assignable.
*
* @var array<int, string>
*/
protected $fillable = [
'name',
'email',
'password',
'url',
'capacity',
'configs',
'configs->default_strategy',
'registered_ip',
'status',
];
/**
* The attributes that should be hidden for serialization.
*
* @var array<int, string>
*/
protected $hidden = [
'password',
'remember_token',
'configs',
'group_id',
'is_adminer',
];
/**
* The attributes that should be cast.
*
* @var array<string, string>
*/
protected $casts = [
'id' => 'integer',
'group_id' => 'integer',
'image_num' => 'integer',
'album_num' => 'integer',
'status' => 'integer',
'capacity' => 'float',
'is_adminer' => 'bool',
'configs' => 'collection',
'email_verified_at' => 'datetime',
];
protected $appends = ['avatar'];
protected static function booted()
{
static::creating(function (self $user) {
// 默认组
$user->group_id = Group::query()->where('is_default', true)->value('id');
// 初始容量
$user->capacity = Utils::config(ConfigKey::UserInitialCapacity);
$user->configs = collect(config('convention.user'))->merge($user->configs ?: []);
});
}
public function avatar(): Attribute
{
return new Attribute(fn () => Utils::getAvatar($this->email));
}
public function useCapacity(): Attribute
{
return new Attribute(fn () => $this->images()->sum('size'));
}
public function group(): BelongsTo
{
return $this->belongsTo(Group::class, 'group_id', 'id');
}
public function albums(): HasMany
{
return $this->hasMany(Album::class, 'user_id', 'id');
}
public function images(): HasMany
{
return $this->hasMany(Image::class, 'user_id', 'id');
}
}

View File

@@ -0,0 +1,56 @@
<?php
namespace App\Providers;
use App\Enums\ConfigKey;
use App\Models\Group;
use App\Utils;
use Illuminate\Support\Facades\Artisan;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Config;
use Illuminate\Support\Facades\View;
use Illuminate\Support\ServiceProvider;
class AppServiceProvider extends ServiceProvider
{
/**
* Register any application services.
*
* @return void
*/
public function register()
{
//
}
/**
* Bootstrap any application services.
*
* @return void
*/
public function boot()
{
// 是否需要生成 env 文件
if (! file_exists(base_path('.env'))) {
file_put_contents(base_path('.env'), file_get_contents(base_path('.env.example')));
// 生成 key
Artisan::call('key:generate');
}
// 如果已经安装程序,初始化一些配置
if (file_exists(base_path('installed.lock'))) {
// 覆盖默认配置
Config::set('app.name', Utils::config(ConfigKey::AppName));
Config::set('mail', array_merge(\config('mail'), Utils::config(ConfigKey::Mail)->toArray()));
View::composer('*', function (\Illuminate\View\View $view) {
/** @var Group $group */
$group = Auth::check() ? Auth::user()->group : Group::query()->where('is_guest', true)->first();
$view->with([
'_group' => $group,
'_is_notice' => strip_tags(Utils::config(ConfigKey::SiteNotice)),
]);
});
}
}
}

View File

@@ -0,0 +1,30 @@
<?php
namespace App\Providers;
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
use Illuminate\Support\Facades\Gate;
class AuthServiceProvider extends ServiceProvider
{
/**
* The policy mappings for the application.
*
* @var array<class-string, class-string>
*/
protected $policies = [
// 'App\Models\Model' => 'App\Policies\ModelPolicy',
];
/**
* Register any authentication / authorization services.
*
* @return void
*/
public function boot()
{
$this->registerPolicies();
//
}
}

View File

@@ -0,0 +1,21 @@
<?php
namespace App\Providers;
use Illuminate\Support\Facades\Broadcast;
use Illuminate\Support\ServiceProvider;
class BroadcastServiceProvider extends ServiceProvider
{
/**
* Bootstrap any application services.
*
* @return void
*/
public function boot()
{
Broadcast::routes();
require base_path('routes/channels.php');
}
}

View File

@@ -0,0 +1,32 @@
<?php
namespace App\Providers;
use Illuminate\Auth\Events\Registered;
use Illuminate\Auth\Listeners\SendEmailVerificationNotification;
use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;
use Illuminate\Support\Facades\Event;
class EventServiceProvider extends ServiceProvider
{
/**
* The event listener mappings for the application.
*
* @var array<class-string, array<int, class-string>>
*/
protected $listen = [
Registered::class => [
SendEmailVerificationNotification::class,
],
];
/**
* Register any events for your application.
*
* @return void
*/
public function boot()
{
//
}
}

Some files were not shown because too many files have changed in this diff Show More