Compare commits

...

21 Commits

Author SHA1 Message Date
067889f096
Merge pull request 'dev' (#7) from dev into master
All checks were successful
Gitea Actions Build / Build (push) Successful in 1m40s
Reviewed-on: #7
2025-07-01 23:27:46 +08:00
953a370233
完善翻译 2025-07-01 23:27:03 +08:00
7f8defeee2
test gpg 2025-07-01 22:24:18 +08:00
67d99ccd42 no message 2025-07-01 22:10:15 +08:00
2a6672613a
no message
All checks were successful
Gitea Actions Build / Build (push) Successful in 1m23s
2025-07-01 08:03:23 +08:00
3071859a3d
no message 2025-07-01 10:25:43 +08:00
570c41bc11
Merge pull request '添加 i18n 重写API界面' (#6) from dev into master
All checks were successful
Gitea Actions Build / Build (push) Successful in 1m25s
Reviewed-on: #6
2025-07-01 00:19:07 +08:00
dac6928844
添加 i18n 重写API界面 2025-07-01 00:18:18 +08:00
c51326b698
Merge pull request '添加语言 languageSchema' (#5) from dev into master
All checks were successful
Gitea Actions Build / Build (push) Successful in 1m34s
Reviewed-on: #5
2025-06-30 10:11:00 +08:00
ddaa02da3d
添加语言 languageSchema 2025-06-30 10:10:25 +08:00
704da3a692
Merge pull request '编写一些前端' (#4) from dev into master
All checks were successful
Gitea Actions Build / Build (push) Successful in 1m29s
Reviewed-on: #4
2025-06-29 17:54:30 +08:00
10580a0d63
编写一些前端 2025-06-29 17:52:31 +08:00
2e2898603f
Merge pull request 'dev' (#3) from dev into master
All checks were successful
Gitea Actions Build / Build (push) Successful in 1m15s
Reviewed-on: #3
2025-06-28 23:42:25 +08:00
9de51dd7ee
Merge branch 'dev' of git.mingliqiye.com:mingliqiye/pan-disk into dev
# Conflicts:
#	.gitignore
2025-06-28 23:41:29 +08:00
9d476c338b
no message 2025-06-28 23:38:25 +08:00
bd846f4273
更新 .gitignore 2025-06-28 23:13:56 +08:00
384fbf3cde
Merge pull request 'no message' (#2) from dev into master
All checks were successful
Gitea Actions Build / Build (push) Successful in 1m12s
Reviewed-on: #2
2025-06-28 23:05:13 +08:00
9c9493eb56
no message 2025-06-28 23:04:45 +08:00
3a83354011
Merge pull request '完成前端与后端链接 登录API完成' (#1) from dev into master
Some checks failed
Gitea Actions Build / Build (push) Failing after 1m9s
Reviewed-on: #1
2025-06-28 22:29:31 +08:00
147e5551a7
完成前端与后端链接 登录API完成 2025-06-28 22:28:50 +08:00
21d82c710b
更新 .gitea/workflows/build.yaml
All checks were successful
Gitea Actions Build / Build (push) Successful in 2m23s
2025-06-28 00:36:09 +08:00
114 changed files with 4926 additions and 582 deletions

View File

@ -1,4 +1,4 @@
[*.{js,jsx,mjs,cjs,ts,tsx,mts,cts,vue,css,scss,sass,less,styl,java}]
[*.{js,jsx,mjs,cjs,ts,tsx,mts,cts,vue,css,scss,sass,less,styl}]
charset = utf-8
indent_size = 2
indent_style = space

View File

@ -8,21 +8,14 @@ jobs:
Build:
runs-on: ubuntu-dev
steps:
- name: Check out repository code
uses: https://git.mingliqiye.com/Actions/checkout@v4
- name: switchJdk
- name: Build
run: |
switch-jdk21
. ~/.profile
java -version
- name: build-test
run: |
java -version
npm config set registry https://registry.npmmirror.com
pnpm install
pnpm run build-jar
pnpm run build-jar-auto
- name: Releases
run: |

8
.gitignore vendored
View File

@ -16,9 +16,9 @@ build/
bin/
!**/src/main/**/bin/
!**/src/test/**/bin/
pnpm-workspace.yaml
### IntelliJ IDEA ###
.idea
*.iws
*.iml
*.ipr
@ -39,4 +39,8 @@ node_modules
dist
pnpm-lock.yaml
src/main/resources/static
src/main/resources/html
log
web-src/types
.idea

17
application.yaml Normal file
View File

@ -0,0 +1,17 @@
config:
port: 9963
app-name: pan-disk
app-version: 1.1
data-source:
mysql:
username: pan
password: PhXCRiCfEmiGesEm
host: 10.0.0.4
port: 3307
database: pan
redis:
host: 10.0.0.4
port: 6380
database: 0
username: ''
password: redis_kTJpsa

View File

@ -1,10 +1,12 @@
import java.security.MessageDigest
import java.text.SimpleDateFormat
import java.util.Date
import java.util.*
plugins {
java
id("org.springframework.boot") version "3.5.3"
idea
id("io.freefair.lombok") version "8.4"
id("org.springframework.boot") version "3.5.0"
id("io.spring.dependency-management") version "1.1.7"
}
val GROUPSID = project.properties["GROUPSID"] as String
@ -20,19 +22,55 @@ version = VERSIONS
val libDir = rootDir.resolve("build").resolve("libs")
val srcDir = rootDir.resolve("src").resolve("main").resolve("java")
val webDir = rootDir.resolve("src").resolve("main").resolve("resources").resolve("static")
val webDir = rootDir.resolve("src").resolve("main").resolve("resources").resolve("html")
java {
toolchain {
languageVersion = JavaLanguageVersion.of(21)
}
}
allprojects {
configurations.all {
exclude(group = "org.springframework.boot", module = "spring-boot-starter-logging")
}
}
tasks.test {
useJUnitPlatform()
jvmArgs = listOf(
"-javaagent:${classpath.find { it.name.contains("mockito-core") }?.absolutePath}",
"-XX:+EnableDynamicAgentLoading",
)
}
dependencies {
implementation("org.springframework.boot:spring-boot-starter")
implementation("org.springframework.boot:spring-boot-starter-log4j2")
implementation("org.springframework.boot:spring-boot-starter-web")
implementation("org.springframework.boot:spring-boot-starter-data-redis")
implementation("org.springframework.boot:spring-boot-starter-actuator")
compileOnly("org.projectlombok:lombok")
testRuntimeOnly("org.projectlombok:lombok")
annotationProcessor("org.projectlombok:lombok")
implementation("org.jetbrains:annotations:24.0.0")
implementation("com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.19.1")
testImplementation("org.springframework.boot:spring-boot-starter-test")
developmentOnly("org.springframework.boot:spring-boot-devtools")
runtimeOnly("com.mysql:mysql-connector-j")
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
implementation("org.springdoc:springdoc-openapi-starter-webmvc-ui:2.8.9")
implementation("cn.dev33:sa-token-redis-jackson:1.44.0")
implementation("cn.hutool:hutool-all:5.8.24")
implementation("cn.dev33:sa-token-jwt:1.44.0")
implementation("org.mindrot:jbcrypt:0.4")
implementation("com.github.f4b6a3:uuid-creator:6.1.0")
implementation("com.squareup.okhttp3:okhttp:5.0.0-alpha.16")
implementation("com.alibaba:druid-spring-boot-3-starter:1.2.25")
implementation("cn.dev33:sa-token-jwt:1.44.0")
implementation("cn.dev33:sa-token-spring-boot3-starter:1.44.0")
implementation("cn.dev33:sa-token-redis-jackson:1.44.0")
implementation("com.baomidou:mybatis-plus-spring-boot3-starter:3.5.12")
}
private fun generateHash(file: File, string: String): String {
@ -60,24 +98,28 @@ private fun getHash(file: File) {
tasks.withType<Test> {
useJUnitPlatform()
}
tasks.register("rmkdir") {
delete(libDir)
mkdir(libDir)
}
tasks.register<Zip>("sources") {
archiveFileName.set(srcJarName)
destinationDirectory.set(libDir)
from(fileTree(srcDir.toString()))
into(".")
}
tasks.register<Zip>("web") {
archiveFileName.set(webZipName)
destinationDirectory.set(libDir)
from(fileTree(webDir.toString()))
into(".")
}
tasks.bootJar {
archiveFileName.set(jarName)
doFirst {
delete(libDir)
mkdir(libDir)
}
manifest {
attributes["Implementation-GroupId"] = GROUPSID
attributes["Implementation-ArtifactId"] = ARTIFACTID
@ -93,9 +135,8 @@ tasks.bootJar {
}
tasks.register("build-jar") {
dependsOn(tasks["sources"])
dependsOn(tasks["web"])
dependsOn(tasks.bootJar)
dependsOn(tasks["rmkdir"], tasks["sources"], tasks["web"], tasks.bootJar)
mustRunAfter(tasks["rmkdir"], tasks["sources"], tasks["web"], tasks.bootJar)
doLast {
getHash(File(libDir, jarName))
getHash(File(libDir, srcJarName))

55
languageSchema.json Normal file
View File

@ -0,0 +1,55 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "https://example.com/language-config.schema.json",
"title": "Language Configuration",
"description": "Schema for validating language configuration files",
"type": "object",
"properties": {
"$schema": {
"type": "string"
},
"iconsUrl": {
"type": "string"
},
"languages": {
"type": "array",
"minItems": 1,
"items": {
"type": "object",
"properties": {
"title": {
"type": "string",
"description": "显示的语言名",
"pattern": "^[\\p{L}\\p{N}\\s-]+$"
},
"id": {
"type": "string",
"description": "语言ID",
"pattern": "^[a-z]{2}-[A-Z]{2}$"
},
"file": {
"type": "string",
"description": "语言文件在language文件夹的位置",
"pattern": "^[a-z]{2}-[A-Z]{2}\\.json$"
},
"icon": {
"type": "string",
"description": "语言的图标",
"pattern": "^.*:.*$"
}
},
"required": [
"title",
"id",
"file",
"icon"
],
"additionalProperties": false
}
}
},
"required": [
"languages"
],
"additionalProperties": false
}

View File

@ -5,9 +5,11 @@
"type": "module",
"scripts": {
"dev": "vite",
"build-jar": "run-p \"build-only\" && gradle -Dorg.gradle.java.home=/opt/jdk/21.0.7/ build-jar",
"build-jar": "run-p \"build-only\" && gradle build-jar",
"build-jar-auto": "run-p \"build-only\" && gradle -Dorg.gradle.java.home=/opt/jdk/21.0.7/ build-jar",
"build": "run-p type-check \"build-only {@}\" --",
"preview": "vite preview",
"build-preview": "run-p build-only && vite preview",
"build-only": "vite build",
"type-check": "vue-tsc --build",
"lint:oxlint": "oxlint . --fix -D correctness --ignore-path .gitignore",
@ -16,27 +18,45 @@
"format": "prettier --write \"**/*.{js,ts,jsx,tsx,cjs,cts,mjs,mts,vue,astro,java}\""
},
"dependencies": {
"@iconify/vue": "^5.0.0",
"@intlify/vue-i18n-core": "^11.1.7",
"@types/vue-router": "^2.0.0",
"@vueuse/core": "^13.4.0",
"naive-ui": "^2.42.0",
"pinia": "^3.0.3",
"swagger-ui-dist": "^5.25.3",
"unplugin-auto-import": "^19.3.0",
"unplugin-vue-components": "^28.8.0",
"vue": "^3.5.17",
"vue-i18n": "11",
"vue-router": "^4.5.1"
},
"devDependencies": {
"@tsconfig/node22": "^22.0.2",
"@types/js-cookie": "^3.0.6",
"@types/node": "^22.15.32",
"@types/swagger-ui-dist": "^3.30.6",
"@vitejs/plugin-vue": "^6.0.0",
"@vue/eslint-config-prettier": "^10.2.0",
"@vue/eslint-config-typescript": "^14.5.1",
"@vue/tsconfig": "^0.7.0",
"alova": "^3.3.3",
"eslint": "^9.29.0",
"eslint-plugin-oxlint": "~1.1.0",
"eslint-plugin-vue": "~10.2.0",
"highlight.js": "^11.11.1",
"jiti": "^2.4.2",
"js-base64": "^3.7.7",
"js-cookie": "^3.0.5",
"npm-run-all2": "^8.0.4",
"openapi-types": "^12.1.3",
"oxlint": "~1.1.0",
"pinia-plugin-persistedstate": "^4.4.0",
"prettier": "3.5.3",
"prettier-plugin-java": "^2.6.8",
"sass-embedded": "^1.89.2",
"typescript": "~5.8.0",
"vite": "npm:rolldown-vite@latest",
"vite": "~6.0.0",
"vite-plugin-vue-devtools": "^7.7.7",
"vue-tsc": "^2.2.10"
}

View File

@ -1,9 +1,13 @@
package com.mingliqiye.disk;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.ConfigurationPropertiesScan;
@SpringBootApplication
@ConfigurationPropertiesScan
@MapperScan("com.mingliqiye.disk.mappers")
public class DiskApplication {
public static void main(String[] args) {

View File

@ -0,0 +1,64 @@
package com.mingliqiye.disk.cache;
import com.mingliqiye.disk.config.ApplicationContextHolder;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import org.apache.ibatis.cache.Cache;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.util.DigestUtils;
public class MyBatisRedisCache implements Cache {
private final String id;
private final RedisTemplate<String, Object> redisTemplate;
private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
public MyBatisRedisCache(String id) {
this.id = id;
this.redisTemplate = ApplicationContextHolder.getBean(RedisTemplate.class);
}
@Override
public String getId() {
return id;
}
@Override
public void putObject(Object key, Object value) {
String redisKey = getRedisKey(key);
redisTemplate.opsForValue().set(redisKey, value, 1, TimeUnit.HOURS); // 设置1小时过期
}
@Override
public Object getObject(Object key) {
String redisKey = getRedisKey(key);
return redisTemplate.opsForValue().get(redisKey);
}
@Override
public Object removeObject(Object key) {
String redisKey = getRedisKey(key);
redisTemplate.delete(redisKey);
return null;
}
@Override
public void clear() {
redisTemplate.delete(redisTemplate.keys(id + ":*"));
}
@Override
public int getSize() {
return 0;
}
@Override
public ReadWriteLock getReadWriteLock() {
return readWriteLock;
}
private String getRedisKey(Object key) {
return (id + ":" + DigestUtils.md5DigestAsHex(String.valueOf(key).getBytes()));
}
}

View File

@ -0,0 +1,30 @@
package com.mingliqiye.disk.config;
import org.jetbrains.annotations.NotNull;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
@Component
public class ApplicationContextHolder implements ApplicationContextAware {
private static ApplicationContext context;
public static <T> T getBean(Class<T> name) {
return context.getBean(name);
}
public static <T> T getBean(String beanName) {
return (T) context.getBean(beanName);
}
public static <T> T getBean(String beanName, Class<T> beanType) {
return context.getBean(beanName, beanType);
}
@Override
public void setApplicationContext(@NotNull ApplicationContext applicationContext) throws BeansException {
context = applicationContext;
}
}

View File

@ -0,0 +1,49 @@
package com.mingliqiye.disk.config;
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import com.mingliqiye.disk.time.DateTime;
import com.mingliqiye.disk.util.PanStpUtil;
import com.mingliqiye.disk.uuid.UUID;
import java.util.Objects;
import org.apache.ibatis.reflection.MetaObject;
import org.springframework.context.annotation.Configuration;
@Configuration
public class MyBatisPlusMetaObjectHandler implements MetaObjectHandler {
private static final String CREATE_USER_ID = "createUserId";
private static final String CREATE_TIME = "createTime";
private static final String UPDATE_USER_ID = "updateUserId";
private static final String UPDATE_TIME = "updateTime";
private static final UUID SYSTEM_ID_UUID = UUID.ofString("48126760-5483-11F0-B90C-6D1CD960F6F3");
@Override
public void insertFill(MetaObject metaObject) {
if (metaObject.hasSetter(CREATE_USER_ID)) {
this.strictInsertFill(
metaObject,
CREATE_USER_ID,
UUID.class,
Objects.requireNonNullElse(PanStpUtil.getLoginIdDefaultNull(), SYSTEM_ID_UUID)
);
}
if (metaObject.hasSetter(CREATE_TIME)) {
this.strictInsertFill(metaObject, CREATE_TIME, DateTime.class, DateTime.now());
}
}
@Override
public void updateFill(MetaObject metaObject) {
if (metaObject.hasSetter(UPDATE_USER_ID)) {
this.strictUpdateFill(
metaObject,
UPDATE_USER_ID,
UUID.class,
Objects.requireNonNullElse(PanStpUtil.getLoginIdDefaultNull(), SYSTEM_ID_UUID)
);
}
if (metaObject.hasSetter(UPDATE_TIME)) {
this.strictUpdateFill(metaObject, UPDATE_TIME, DateTime.class, DateTime.now());
}
}
}

View File

@ -0,0 +1,23 @@
package com.mingliqiye.disk.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
@Configuration
public class RedisConfig {
@Primary
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(connectionFactory);
template.setKeySerializer(new StringRedisSerializer());
template.setValueSerializer(new GenericJackson2JsonRedisSerializer());
return template;
}
}

View File

@ -0,0 +1,23 @@
package com.mingliqiye.disk.config;
import cn.dev33.satoken.interceptor.SaInterceptor;
import cn.dev33.satoken.jwt.StpLogicJwtForSimple;
import cn.dev33.satoken.stp.StpLogic;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class SaTokenConfigure implements WebMvcConfigurer {
@Bean
public StpLogic getStpLogicJwt() {
return new StpLogicJwtForSimple();
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new SaInterceptor()).addPathPatterns("/**");
}
}

View File

@ -0,0 +1,63 @@
package com.mingliqiye.disk.config;
import com.mingliqiye.disk.configuration.Config;
import io.swagger.v3.oas.annotations.ExternalDocumentation;
import io.swagger.v3.oas.annotations.OpenAPIDefinition;
import io.swagger.v3.oas.annotations.enums.SecuritySchemeIn;
import io.swagger.v3.oas.annotations.enums.SecuritySchemeType;
import io.swagger.v3.oas.annotations.security.SecurityScheme;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.info.Contact;
import io.swagger.v3.oas.models.info.Info;
import io.swagger.v3.oas.models.info.License;
import io.swagger.v3.oas.models.servers.Server;
import java.util.ArrayList;
import java.util.List;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Slf4j
@OpenAPIDefinition(
externalDocs = @ExternalDocumentation(
description = "@git.mingliqiye",
url = "https://git.mingliqiye.com/mingliqiye/pan-disk"
)
)
@SecurityScheme(
name = "Authorization-Bearer-Token", // 认证方案名称
type = SecuritySchemeType.HTTP, // 认证类型当前为http认证
description = "Authorization: bearer {token}", // 描述信息
in = SecuritySchemeIn.HEADER, // 代表在http请求头部
scheme = "bearer", // 认证方案Authorization: bearer token信息
bearerFormat = "JWT"
) // 表示使用 JWT 格式作为 Bearer Token 的格式
@Configuration
public class SpringDocConfig {
private final Config config;
public SpringDocConfig(Config config) {
this.config = config;
}
@Bean
public OpenAPI openAPI() {
List<Server> servers = new ArrayList<>();
servers.add(new Server().description("当前网页").url("/"));
return new OpenAPI().info(this.getApiInfo()).servers(servers);
}
private Info getApiInfo() {
return new Info()
.title(config.getAppName())
.version(config.getAppVersion())
.description("SpringBoot3 %s-V%s Swagger3 ApiDoc".formatted(config.getAppName(), config.getAppVersion()))
.contact(
new Contact().name("mingliqiye").url("https://www.mingliqiye.com").email("minglipro@mingliqiye.com")
)
.license(new License().name("Apache 2.0").url("https://www.apache.org/licenses/LICENSE-2.0"))
.summary("ApiDoc")
.termsOfService("https://pan.mingliqiye.com/");
}
}

View File

@ -0,0 +1,42 @@
package com.mingliqiye.disk.config;
import cn.dev33.satoken.stp.StpInterface;
import com.mingliqiye.disk.mappers.UserMapper;
import com.mingliqiye.disk.model.User;
import com.mingliqiye.disk.uuid.UUID;
import java.util.ArrayList;
import java.util.List;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Configuration;
@Slf4j
@Configuration
public class StpInterfaceImpl implements StpInterface {
private final UserMapper userMapper;
public StpInterfaceImpl(UserMapper userMapper) {
this.userMapper = userMapper;
}
/**
* 返回一个账号所拥有的权限码集合
*/
@Override
public List<String> getPermissionList(Object loginId, String loginType) {
User user = userMapper.selectById(UUID.ofString((String) loginId));
List<String> list = new ArrayList<>(user.getPrermissions());
if (user.isAdmin()) list.add("*.*.*");
user.getRoles().forEach(i -> list.add(String.format("%s.*.*", i)));
log.info(String.valueOf(list));
return list;
}
/**
* 返回一个账号所拥有的角色标识集合 (权限与角色可分开校验)
*/
@Override
public List<String> getRoleList(Object loginId, String loginType) {
return userMapper.selectById(UUID.ofString((String) loginId)).getRoles();
}
}

View File

@ -0,0 +1,14 @@
package com.mingliqiye.disk.configuration;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
@Data
@ConfigurationProperties(prefix = "config")
public class Config {
private String appName;
private String appVersion;
private Integer port;
private DataSource dataSource;
}

View File

@ -0,0 +1,12 @@
package com.mingliqiye.disk.configuration;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
@Data
@ConfigurationProperties(prefix = "config.data-source")
public class DataSource {
private Mysql mysql;
private Redis redis;
}

View File

@ -0,0 +1,15 @@
package com.mingliqiye.disk.configuration;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
@Data
@ConfigurationProperties(prefix = "config.data-source.mysql")
public class Mysql {
private String host;
private Integer port;
private String database;
private String username;
private String password;
}

View File

@ -0,0 +1,15 @@
package com.mingliqiye.disk.configuration;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
@Data
@ConfigurationProperties(prefix = "config.data-source.redis")
public class Redis {
private String host;
private Integer port;
private Integer database;
private String username;
private String password;
}

View File

@ -0,0 +1,56 @@
package com.mingliqiye.disk.controller;
import cn.dev33.satoken.annotation.SaCheckLogin;
import cn.dev33.satoken.stp.StpUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.mingliqiye.disk.dto.auth.Login;
import com.mingliqiye.disk.exception.InternalServerException;
import com.mingliqiye.disk.http.Respose;
import com.mingliqiye.disk.mappers.UserMapper;
import com.mingliqiye.disk.model.User;
import com.mingliqiye.disk.util.PanStpUtil;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import lombok.extern.slf4j.Slf4j;
import org.mindrot.jbcrypt.BCrypt;
import org.springframework.web.bind.annotation.*;
@Slf4j
@RestController
@RequestMapping("/apis/auth")
@Tag(name = "权限管理", description = "权限和用户管理")
public class AuthController {
private final UserMapper userMapper;
public AuthController(UserMapper userMapper) {
this.userMapper = userMapper;
}
@Operation(summary = "登陆")
@PostMapping("/login")
public Respose<String> login(@Valid @RequestBody Login loginBody) {
User user = userMapper.selectOne(new QueryWrapper<User>().eq("username", loginBody.getUsername()));
if (user != null && BCrypt.checkpw(loginBody.getPassword(), user.getPassword())) {
return Respose.builder(PanStpUtil.login(user.getId()));
}
throw new InternalServerException("用户名或密码错误");
}
@Operation(summary = "获取当前登陆用户的信息")
@SecurityRequirement(name = "Authorization-Bearer-Token")
@GetMapping("/who-is-me")
public Respose<User> whoIsMe() {
return Respose.builder().setData(userMapper.selectById(PanStpUtil.getLoginId()).setPasswordNull());
}
@Operation(summary = "登出", security = @SecurityRequirement(name = "Authorization-Bearer-Token"))
@SaCheckLogin
@DeleteMapping("/logout")
public Respose<Object> logout() {
StpUtil.logout();
return Respose.builder();
}
}

View File

@ -0,0 +1,28 @@
package com.mingliqiye.disk.controller;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import java.io.InputStream;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
@RestController
@RequestMapping
@Tag(name = "前端路由", description = "统一匹配路径指向VueRouter")
public class IndexController {
@Operation(summary = "VueRouter 主路由")
@GetMapping(value = { "/", "/{path:^(?!static|apis|blob).*$}/**" })
public ResponseEntity<StreamingResponseBody> index() {
StreamingResponseBody streamingResponseBody = s -> {
try (InputStream stream = this.getClass().getResourceAsStream("/html/index.html")) {
if (stream != null) {
stream.transferTo(s);
}
}
};
return ResponseEntity.ok().body(streamingResponseBody);
}
}

View File

@ -0,0 +1,28 @@
package com.mingliqiye.disk.controller;
import com.mingliqiye.disk.configuration.Config;
import com.mingliqiye.disk.dto.system.Info;
import com.mingliqiye.disk.http.Respose;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/apis/system")
@Tag(name = "系统路由", description = "访问系统的一些功能")
public class SystemController {
private final Config config;
public SystemController(Config config) {
this.config = config;
}
@Operation(summary = "系统信息")
@GetMapping("/info")
public Respose<Info> info() {
return Respose.builder(new Info(config.getAppName(), config.getAppVersion()));
}
}

View File

@ -0,0 +1,14 @@
package com.mingliqiye.disk.dto.auth;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
@Data
public class Login {
@NotNull(message = "用户名不能为空")
private String username;
@NotNull(message = "用户密码不能为空")
private String password;
}

View File

@ -0,0 +1,12 @@
package com.mingliqiye.disk.dto.system;
import lombok.AllArgsConstructor;
import lombok.Data;
@Data
@AllArgsConstructor
public class Info {
private String appName;
private String appVersion;
}

View File

@ -0,0 +1,34 @@
package com.mingliqiye.disk.exception;
import java.io.Serializable;
public class BaseException extends RuntimeException implements BaseExceptionInterface, Serializable {
private Integer code;
private String message;
public BaseException(String message, Integer code, Throwable throwable) {
super(message, throwable);
this.code = code;
this.message = message;
}
public BaseException(String message, Integer code) {
this(message, code, null);
}
@Override
public Integer getCode() {
return code;
}
@Override
public Throwable getThrowable() {
return this.getCause();
}
@Override
public String getMessage() {
return message;
}
}

View File

@ -0,0 +1,102 @@
package com.mingliqiye.disk.exception;
import cn.dev33.satoken.exception.NotLoginException;
import cn.dev33.satoken.exception.NotPermissionException;
import cn.dev33.satoken.exception.NotRoleException;
import com.mingliqiye.disk.http.Respose;
import com.mingliqiye.disk.util.StringUtil;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.support.DefaultMessageSourceResolvable;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.servlet.NoHandlerFoundException;
@Slf4j
@RestControllerAdvice
public class BaseExceptionHandler {
@ExceptionHandler(BaseException.class)
public ResponseEntity<Respose<?>> exceptionHandler(BaseException e) {
return ResponseEntity.status(e.getCode()).body(
Respose.builder().setCode(e.getCode()).setMessage(StringUtil.format("{}", e.getMessage()))
);
}
@ExceptionHandler(HttpRequestMethodNotSupportedException.class)
public Respose<?> exceptionHandler(HttpRequestMethodNotSupportedException e) {
return Respose.builder()
.setCode(ExceptionCode.ERROR_METHOD_NOT_ALLOWED.getValue())
.setMessage(StringUtil.format("{} by {}", e.getMessage(), e.getClass().getName()));
}
@ExceptionHandler(NoHandlerFoundException.class)
public ResponseEntity<Respose<?>> exceptionHandler(NoHandlerFoundException e) {
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(
Respose.builder()
.setCode(ExceptionCode.ERROR_NOT_FOUND.getValue())
.setMessage(StringUtil.format("{} by {}", e.getMessage(), e.getClass().getName()))
);
}
@ExceptionHandler(NotLoginException.class)
public ResponseEntity<Respose<?>> exceptionHandler(NotLoginException e, HttpServletRequest request) {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(
Respose.builder()
.setCode(ExceptionCode.ERROR_UNAUTHORIZED.getValue())
.setMessage(e.getMessage())
.setData(e.getType())
);
}
@ExceptionHandler(HttpMessageNotReadableException.class)
public ResponseEntity<Respose<?>> exceptionHandler(HttpMessageNotReadableException e) {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(
Respose.builder().setCode(ExceptionCode.ERROR_FORBIDDEN.getValue()).setMessage(e.getMessage())
);
}
@ExceptionHandler(NotRoleException.class)
public ResponseEntity<Respose<?>> exceptionHandler(NotRoleException e) {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(
Respose.builder()
.setCode(ExceptionCode.ERROR_FORBIDDEN.getValue())
.setMessage(e.getMessage())
.setData(e.getCode())
);
}
@ExceptionHandler(NotPermissionException.class)
public Respose<?> exceptionHandler(NotPermissionException e) {
return Respose.builder()
.setCode(ExceptionCode.ERROR_FORBIDDEN.getValue())
.setMessage(e.getMessage())
.setData(e.getCode());
}
@ExceptionHandler(MethodArgumentNotValidException.class)
public Respose<?> exceptionHandler(MethodArgumentNotValidException e) {
return Respose.builder()
.setCode(ExceptionCode.ERROR_FORBIDDEN.getValue())
.setMessage(
StringUtil.join(
",",
e.getBindingResult().getFieldErrors(),
DefaultMessageSourceResolvable::getDefaultMessage
)
);
}
@ExceptionHandler(Exception.class)
public Respose<?> exceptionHandler(Exception e) {
log.error(e.getMessage(), e);
return Respose.builder()
.setCode(ExceptionCode.ERROR_INTERNAL_SERVER.getValue())
.setMessage(StringUtil.format("{} by {}", e.getMessage(), e.getClass().getName()));
}
}

View File

@ -0,0 +1,19 @@
package com.mingliqiye.disk.exception;
import java.io.Serializable;
public interface BaseExceptionInterface extends Serializable {
String getMessage();
Integer getCode();
StackTraceElement[] getStackTrace();
Throwable getThrowable();
String toString();
default String getClassName() {
return this.getClass().getName();
}
}

View File

@ -0,0 +1,29 @@
package com.mingliqiye.disk.exception;
import lombok.Getter;
public enum ExceptionCode {
ERROR_INTERNAL_SERVER(500),
ERROR_PAYMENT_REQUIRED(402),
ERROR_NOT_FOUND(404),
ERROR_METHOD_NOT_ALLOWED(405),
ERROR_UNAUTHORIZED(401),
ERROR_FORBIDDEN(403),
OK(200);
@Getter
private final int value;
ExceptionCode(int value) {
this.value = value;
}
public static ExceptionCode getExceptionCode(int value) {
for (ExceptionCode exceptionCode : ExceptionCode.values()) {
if (exceptionCode.value == value) {
return exceptionCode;
}
}
return null;
}
}

View File

@ -0,0 +1,14 @@
package com.mingliqiye.disk.exception;
public class InternalServerException extends BaseException {
private static final Integer code = ExceptionCode.ERROR_INTERNAL_SERVER.getValue();
public InternalServerException(String message, Throwable throwable) {
super(message, code, throwable);
}
public InternalServerException(String message) {
super(message, code);
}
}

View File

@ -0,0 +1,14 @@
package com.mingliqiye.disk.exception;
public class NotFoundException extends BaseException {
private static final Integer code = ExceptionCode.ERROR_NOT_FOUND.getValue();
public NotFoundException(String message, Throwable throwable) {
super(message, code, throwable);
}
public NotFoundException(String message) {
super(message, code);
}
}

View File

@ -0,0 +1,6 @@
package com.mingliqiye.disk.function;
@FunctionalInterface
public interface P1Function<P> {
void call(P p);
}

View File

@ -0,0 +1,6 @@
package com.mingliqiye.disk.function;
@FunctionalInterface
public interface P1R1Function<P, R> {
R call(P p);
}

View File

@ -0,0 +1,82 @@
package com.mingliqiye.disk.http;
import com.mingliqiye.disk.exception.ExceptionCode;
import com.mingliqiye.disk.time.DateTime;
public class Respose<T> {
private int code = ExceptionCode.OK.getValue();
private String message = "操作成功";
private T data;
private DateTime dateTime = DateTime.now();
public Respose(int code, String message, T data, DateTime dateTime) {
this.code = code;
this.message = message;
this.data = data;
this.dateTime = dateTime;
}
public Respose(int code, String message, T data) {
this.code = code;
this.message = message;
this.data = data;
}
public Respose(int code, String message) {
this.code = code;
this.message = message;
}
public Respose(T data) {
this.data = data;
}
public Respose() {}
public static <T> Respose<T> builder() {
return new Respose<>();
}
public static <T> Respose<T> builder(T data) {
return new Respose<>().setData(data);
}
public static <T> Respose<T> error(Class<T> type, Integer code, String message) {
return new Respose<T>().setCode(code).setMessage(message);
}
public static <T> Respose<T> error(Class<T> type, ExceptionCode code, String message) {
return error(type, code.getValue(), message);
}
public String getMessage() {
return message;
}
public Respose<T> setMessage(String message) {
this.message = message;
return this;
}
public int getCode() {
return code;
}
public Respose<T> setCode(int code) {
this.code = code;
return this;
}
public DateTime getDateTime() {
return dateTime;
}
public T getData() {
return data;
}
public <TD> Respose<TD> setData(TD data) {
return new Respose<>(code, message, data);
}
}

View File

@ -0,0 +1,9 @@
package com.mingliqiye.disk.mappers;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.mingliqiye.disk.cache.MyBatisRedisCache;
import com.mingliqiye.disk.model.User;
import org.apache.ibatis.annotations.CacheNamespace;
@CacheNamespace(implementation = MyBatisRedisCache.class)
public interface UserMapper extends BaseMapper<User> {}

View File

@ -0,0 +1,53 @@
package com.mingliqiye.disk.model;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
import com.mingliqiye.disk.time.DateTime;
import com.mingliqiye.disk.uuid.UUID;
import java.util.List;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
@TableName(value = "users", autoResultMap = true)
public class User {
private UUID id;
private String username;
private String password;
private String nickname;
@TableField(typeHandler = JacksonTypeHandler.class)
private List<String> prermissions;
@TableField(typeHandler = JacksonTypeHandler.class)
private List<String> roles;
private byte[] icon;
private boolean admin;
@TableField(fill = FieldFill.INSERT)
private UUID createUserId;
@TableField(fill = FieldFill.INSERT)
private DateTime createTime;
@TableField(fill = FieldFill.INSERT_UPDATE)
private UUID updateUserId;
@TableField(fill = FieldFill.INSERT_UPDATE)
private DateTime updateTime;
public User setPasswordNull() {
setPassword("***");
return this;
}
}

View File

@ -0,0 +1,241 @@
package com.mingliqiye.disk.time;
import com.fasterxml.jackson.annotation.JsonView;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import io.swagger.v3.oas.annotations.media.Schema;
import java.io.Serializable;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.Date;
import lombok.Getter;
import lombok.Setter;
/**
* com.mingliqiye.libs.Main
* <p>自定义时间类
*
* @author MingLiPro|Armamem0t
* @version 1.0
* @see LocalDateTime
* @see Date
* @see ZoneId
*/
@JsonSerialize(using = DateTimeJsonSerializer.class)
@JsonDeserialize(using = DateTimeJsonDeserializer.class)
@Schema(type = "String")
@JsonView(String.class)
public class DateTime implements Serializable {
@Getter
@Setter
private LocalDateTime localDateTime;
@Getter
@Setter
private ZoneId zoneId = ZoneId.systemDefault();
DateTime() {
this(Instant.now());
}
DateTime(Instant instant) {
this(LocalDateTime.ofInstant(instant, ZoneId.systemDefault()));
}
DateTime(LocalDateTime localDateTime) {
this.localDateTime = localDateTime;
}
DateTime(Instant instant, ZoneId zoneId) {
this(LocalDateTime.ofInstant(instant, zoneId));
}
DateTime(Date date) {
this(date.toInstant());
}
public static DateTime now() {
return new DateTime();
}
public static DateTime ofCurrentTimeMillis(long time) {
return new DateTime(Instant.ofEpochMilli(time));
}
public static DateTime of(Instant instant) {
return new DateTime(instant);
}
public static String format(Date date, String formater) {
return format(toLocalDateTime(date), formater);
}
public static String format(LocalDateTime date, String formater) {
return DateTimeFormatter.ofPattern(formater).format(date);
}
public static LocalDateTime toLocalDateTime(Date date) {
return LocalDateTime.ofInstant(date.toInstant(), ZoneId.systemDefault());
}
public static String format(LocalDateTime date, Formats formats) {
return format(date, formats.value);
}
public static DateTime parse(String timestr, Formats formater) {
return parse(timestr, formater.value);
}
public static DateTime parse(String timestr, String formater) {
DateTimeFormatter sdf = DateTimeFormatter.ofPattern(formater);
return of(LocalDateTime.parse(timestr, sdf));
}
public static DateTime of(LocalDateTime localDateTime) {
return new DateTime(localDateTime);
}
public static void main(String[] args) {}
@Override
public String toString() {
return (
this.getClass().getName() +
'@' +
Integer.toHexString(hashCode()) +
'(' +
format(Formats.STANDARD_DATETIME_MILLISECOUND7) +
")"
);
}
public String format(Formats formater) {
return format(localDateTime, formater.value);
}
public Instant toInstant() {
return toInstant(localDateTime);
}
public static Instant toInstant(LocalDateTime localDateTime) {
return toInstant(localDateTime, ZoneId.systemDefault());
}
public static Instant toInstant(LocalDateTime localDateTime, ZoneId zoneId) {
return localDateTime.atZone(zoneId).toInstant();
}
public Instant toInstant(ZoneId zoneId) {
return toInstant(localDateTime, zoneId);
}
public Date toDate() {
return toDate(localDateTime);
}
public static Date toDate(LocalDateTime localDateTime) {
return Date.from(toInstant(localDateTime));
}
public String format(String formater) {
return format(localDateTime, formater);
}
public DateTime plusYears(long years) {
return plusYears(this, years);
}
public static DateTime plusYears(DateTime dateTime, long years) {
dateTime.setLocalDateTime(dateTime.getLocalDateTime().plusYears(years));
return dateTime;
}
public DateTime plusMonths(long months) {
return plusMonths(this, months);
}
public static DateTime plusMonths(DateTime dateTime, long months) {
dateTime.setLocalDateTime(dateTime.getLocalDateTime().plusMonths(months));
return dateTime;
}
public DateTime plusWeeks(long weeks) {
return plusWeeks(this, weeks);
}
public static DateTime plusWeeks(DateTime dateTime, long weeks) {
dateTime.setLocalDateTime(dateTime.getLocalDateTime().plusWeeks(weeks));
return dateTime;
}
public DateTime plusDays(long days) {
return plusDays(this, days);
}
public static DateTime plusDays(DateTime dateTime, long days) {
dateTime.setLocalDateTime(dateTime.getLocalDateTime().plusDays(days));
return dateTime;
}
public DateTime plusHours(long hours) {
return plusHours(this, hours);
}
public static DateTime plusHours(DateTime dateTime, long hours) {
dateTime.setLocalDateTime(dateTime.getLocalDateTime().plusHours(hours));
return dateTime;
}
public DateTime plusMinutes(long minutes) {
return plusMinutes(this, minutes);
}
public static DateTime plusMinutes(DateTime dateTime, long minutes) {
dateTime.setLocalDateTime(dateTime.getLocalDateTime().plusMinutes(minutes));
return dateTime;
}
public DateTime plusSeconds(long seconds) {
return plusSeconds(this, seconds);
}
public static DateTime plusSeconds(DateTime dateTime, long seconds) {
dateTime.setLocalDateTime(dateTime.getLocalDateTime().plusSeconds(seconds));
return dateTime;
}
public DateTime plusNanos(long nanos) {
return plusNanos(this, nanos);
}
public static DateTime plusNanos(DateTime dateTime, long nanos) {
dateTime.setLocalDateTime(dateTime.getLocalDateTime().plusNanos(nanos));
return dateTime;
}
@Getter
public enum Formats {
STANDARD_DATETIME("yyyy-MM-dd HH:mm:ss"),
STANDARD_DATETIME_MILLISECOUND7("yyyy-MM-dd HH:mm:ss.SSSSSSS"),
STANDARD_DATETIME_MILLISECOUND6("yyyy-MM-dd HH:mm:ss.SSSSSS"),
STANDARD_DATETIME_MILLISECOUND5("yyyy-MM-dd HH:mm:ss.SSSSS"),
STANDARD_DATETIME_MILLISECOUND4("yyyy-MM-dd HH:mm:ss.SSSS"),
STANDARD_DATETIME_MILLISECOUND3("yyyy-MM-dd HH:mm:ss.SSS"),
STANDARD_DATETIME_MILLISECOUND2("yyyy-MM-dd HH:mm:ss.SS"),
STANDARD_DATETIME_MILLISECOUND1("yyyy-MM-dd HH:mm:ss.S"),
STANDARD_ISO("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"),
STANDARD_DATETIME_SECOUND("yyyy-MM-dd HH:mm:ss"),
STANDARD_DATE("yyyy-MM-dd"),
ISO8601("yyyy-MM-dd'T'HH:mm:ss.SSS'000'"),
COMPACT_DATETIME("yyyyMMddHHmmss");
private final String value;
Formats(String value) {
this.value = value;
}
}
}

View File

@ -0,0 +1,17 @@
package com.mingliqiye.disk.time;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import java.io.IOException;
public class DateTimeJsonDeserializer extends JsonDeserializer<DateTime> {
@Override
public DateTime deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
if (p.isNaN()) {
return null;
}
return DateTime.parse(p.getValueAsString(), DateTime.Formats.STANDARD_DATETIME_MILLISECOUND6);
}
}

View File

@ -0,0 +1,33 @@
package com.mingliqiye.disk.time;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonToken;
import com.fasterxml.jackson.core.type.WritableTypeId;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.jsontype.TypeSerializer;
import java.io.IOException;
public class DateTimeJsonSerializer extends JsonSerializer<DateTime> {
@Override
public void serialize(DateTime value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
if (value == null) {
gen.writeNull();
return;
}
gen.writeString(value.format(DateTime.Formats.STANDARD_DATETIME_MILLISECOUND6));
}
@Override
public void serializeWithType(
DateTime value,
JsonGenerator gen,
SerializerProvider serializers,
TypeSerializer typeSer
) throws IOException {
WritableTypeId typeId = typeSer.writeTypePrefix(gen, typeSer.typeId(value, JsonToken.VALUE_STRING));
serialize(value, gen, serializers);
typeSer.writeTypeSuffix(gen, typeId);
}
}

View File

@ -0,0 +1,45 @@
package com.mingliqiye.disk.typeHandlers;
import com.mingliqiye.disk.time.DateTime;
import java.sql.*;
import org.apache.ibatis.type.BaseTypeHandler;
import org.apache.ibatis.type.JdbcType;
import org.apache.ibatis.type.MappedJdbcTypes;
import org.apache.ibatis.type.MappedTypes;
@MappedTypes({ DateTime.class })
@MappedJdbcTypes(JdbcType.VARCHAR)
public class DateTimeTypeHandler extends BaseTypeHandler<DateTime> {
@Override
public void setNonNullParameter(PreparedStatement ps, int i, DateTime parameter, JdbcType jdbcType)
throws SQLException {
ps.setTimestamp(i, Timestamp.valueOf(parameter.getLocalDateTime()));
}
@Override
public DateTime getNullableResult(ResultSet rs, String columnName) throws SQLException {
return parse(rs.getString(columnName));
}
@Override
public DateTime getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
return parse(rs.getString(columnIndex));
}
@Override
public DateTime getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
return parse(cs.getString(columnIndex));
}
public DateTime parse(String s) {
if (s == null) {
return null;
}
return DateTime.parse(s, DateTime.Formats.STANDARD_DATETIME_MILLISECOUND6);
}
public String format(DateTime t) {
return t.format(DateTime.Formats.STANDARD_DATETIME_MILLISECOUND6);
}
}

View File

@ -0,0 +1,55 @@
package com.mingliqiye.disk.typeHandlers;
import com.mingliqiye.disk.uuid.UUID;
import java.nio.ByteBuffer;
import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import org.apache.ibatis.type.BaseTypeHandler;
import org.apache.ibatis.type.JdbcType;
import org.apache.ibatis.type.MappedJdbcTypes;
import org.apache.ibatis.type.MappedTypes;
@MappedTypes({ UUID.class })
@MappedJdbcTypes(JdbcType.BINARY)
public class UUIDBinaryTypeHandler extends BaseTypeHandler<UUID> {
public static byte[] UUID_TO_BIN(UUID uuid) {
ByteBuffer bb = ByteBuffer.wrap(new byte[16]);
bb.putLong(uuid.GetUUID().getMostSignificantBits());
bb.putLong(uuid.GetUUID().getLeastSignificantBits());
return bb.array();
}
public static UUID BIN_TO_UUID(byte[] bytes) {
if (bytes == null) {
return null;
}
ByteBuffer bb = ByteBuffer.wrap(bytes);
long mostSig = bb.getLong();
long leastSig = bb.getLong();
return new UUID(mostSig, leastSig);
}
@Override
public void setNonNullParameter(PreparedStatement ps, int i, UUID parameter, JdbcType jdbcType)
throws SQLException {
ps.setBytes(i, UUID_TO_BIN(parameter));
}
@Override
public UUID getNullableResult(ResultSet rs, String columnName) throws SQLException {
return BIN_TO_UUID(rs.getBytes(columnName));
}
@Override
public UUID getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
return BIN_TO_UUID(rs.getBytes(columnIndex));
}
@Override
public UUID getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
return BIN_TO_UUID(cs.getBytes(columnIndex));
}
}

View File

@ -0,0 +1,264 @@
package com.mingliqiye.disk.util;
import com.mingliqiye.disk.function.P1Function;
import java.io.*;
import java.nio.channels.Channels;
import java.nio.channels.FileChannel;
import java.nio.channels.WritableByteChannel;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class FileUtil {
public static String save(InputStream inputStream, Path filepath, P1Function<Long> callback) {
return save(inputStream, filepath.toString(), callback);
}
public static String save(InputStream inputStream, String filepath, P1Function<Long> callback) {
Path filePath = Paths.get(filepath);
Path path1 = filePath.getParent();
if (!path1.toFile().exists()) {
path1.toFile().mkdirs();
}
byte[] buffer = new byte[1024];
int len;
long total = 0;
try {
filePath.toFile().createNewFile();
} catch (IOException e) {
e.printStackTrace();
throw new RuntimeException(e);
}
try (FileOutputStream fileOutputStream = new FileOutputStream(filePath.toFile())) {
while ((len = inputStream.read(buffer)) != -1) {
fileOutputStream.write(buffer, 0, len);
total += len;
if (callback != null) {
callback.call(total);
}
}
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
return null;
}
return filePath.toAbsolutePath().toString();
}
public static String save(InputStream inputStream, Path filepath) {
return save(inputStream, filepath.toString(), null);
}
public static String save(InputStream inputStream, String filepath) {
return save(inputStream, filepath, null);
}
public static boolean copy(String fromCopy, String toCopyed) {
return copy(Path.of(fromCopy), Path.of(toCopyed));
}
public static boolean copy(Path fromCopy, Path toCopyed) {
return copy(fromCopy.toFile(), toCopyed.toFile());
}
public static boolean copy(File fromCopy, File toCopyed) {
try (InputStream inputStream = new FileInputStream(fromCopy)) {
try (OutputStream outputStream = new FileOutputStream(toCopyed)) {
inputStream.transferTo(outputStream);
return true;
}
} catch (IOException e) {
return false;
}
}
public static String saveTxt(String txt, String path) {
return saveTxt(txt, Paths.get(path));
}
public static String saveTxt(String txt, Path path) {
return saveTxt(txt, path.toFile());
}
public static String saveTxt(String txt, File path) {
try (OutputStream outputStream = new FileOutputStream(path)) {
outputStream.write(txt.getBytes());
} catch (IOException e) {
return null;
}
return path.getAbsolutePath();
}
public static byte[] readBytes(String path) {
return readBytes(Paths.get(path));
}
public static byte[] readBytes(Path path) {
return readBytes(path.toFile());
}
public static byte[] readBytes(File f) {
try (FileInputStream fileInputStream = new FileInputStream(f)) {
return readBytes(fileInputStream);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public static byte[] readBytes(InputStream inputStream) {
try (ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream()) {
inputStream.transferTo(byteArrayOutputStream);
return byteArrayOutputStream.toByteArray();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public static String readString(String path) {
return readString(Paths.get(path));
}
public static String readString(Path path) {
return readString(path.toFile());
}
public static String readString(File path) {
byte[] bytes = readBytes(path);
return new String(bytes);
}
public static String readString(InputStream path) {
byte[] bytes = readBytes(path);
return new String(bytes);
}
public static boolean delete(Path filepath) {
try {
Files.delete(filepath);
return true;
} catch (IOException e) {
return false;
}
}
public static void main(String[] args) {
log.info("This is a test message");
}
/**
* 文件范围读取工具类包含起始和结束位置
* 有效范围from [0, filesize-1], to [from, filesize-1]
*/
public class FileRangeReader {
/**
* 读取文件指定范围内容到输出流
*
* @param filePath 文件路径
* @param from 起始位置包含0-based
* @param to 结束位置包含
* @param output 输出流
* @throws IOException 如果发生I/O错误
* @throws IllegalArgumentException 如果范围无效
*/
public static void read(String filePath, long from, long to, OutputStream output)
throws IOException, IllegalArgumentException {
// 参数验证
if (from < 0) {
throw new IllegalArgumentException("起始位置不能小于0");
}
if (to < from) {
throw new IllegalArgumentException("结束位置不能小于起始位置");
}
Path path = Paths.get(filePath);
try (FileChannel fileChannel = FileChannel.open(path, StandardOpenOption.READ)) {
long fileSize = fileChannel.size();
// 自动调整超出文件范围的请求
if (from >= fileSize) {
return; // 起始位置已超出文件范围不读取任何内容
}
if (to >= fileSize) {
to = fileSize - 1; // 调整结束位置为文件末尾
}
try (WritableByteChannel outChannel = Channels.newChannel(output)) {
long position = from;
long remaining = to - from + 1; // +1因为包含结束位置
// 使用零拷贝技术传输数据
while (remaining > 0) {
long transferred = fileChannel.transferTo(position, remaining, outChannel);
if (transferred <= 0) break;
position += transferred;
remaining -= transferred;
}
}
}
}
/**
* 读取文件全部内容到输出流
*
* @param filePath 文件路径
* @param output 输出流
* @throws IOException 如果发生I/O错误
*/
public static void readFull(String filePath, OutputStream output) throws IOException {
Path path = Paths.get(filePath);
try (FileChannel fileChannel = FileChannel.open(path, StandardOpenOption.READ)) {
long fileSize = fileChannel.size();
if (fileSize > 0) {
read(filePath, 0, fileSize - 1, output);
}
}
}
/**
* 读取文件指定范围内容到字节数组
*
* @param filePath 文件路径
* @param from 起始位置
* @param to 结束位置包含
* @return 读取的字节数组
* @throws IOException 如果发生I/O错误
*/
public static byte[] readToByteArray(String filePath, long from, long to) throws IOException {
try (ByteArrayOutputStream output = new ByteArrayOutputStream()) {
read(filePath, from, to, output);
return output.toByteArray();
}
}
/**
* 读取文件全部内容到字节数组
*
* @param filePath 文件路径
* @return 文件内容的字节数组
* @throws IOException 如果发生I/O错误
*/
public static byte[] readFullToByteArray(String filePath) throws IOException {
try (ByteArrayOutputStream output = new ByteArrayOutputStream()) {
readFull(filePath, output);
return output.toByteArray();
}
}
/**
* 获取文件大小
*
* @param filePath 文件路径
* @return 文件大小字节数
* @throws IOException 如果发生I/O错误
*/
public static long getFileSize(String filePath) throws IOException {
return Paths.get(filePath).toFile().length();
}
}
}

View File

@ -0,0 +1,3 @@
package com.mingliqiye.disk.util;
public class HashUtil {}

View File

@ -0,0 +1,17 @@
package com.mingliqiye.disk.util;
import java.util.ArrayList;
import java.util.List;
public class ListUtil {
public static List<String> toStringList(Object[] object) {
List<String> list = new ArrayList<>();
for (Object obj : object) {
list.add(obj.toString());
}
return list;
}
public static class StringList extends ArrayList<String> {}
}

View File

@ -0,0 +1,78 @@
package com.mingliqiye.disk.util;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
/**
* 本地主机工具类
*
* @author MingliPro
* @since 2019年11月13日09:04:36
*/
public class LocalHostUtil {
/**
* 获取主机名称
*
* @return
* @throws UnknownHostException
*/
public static String getHostName() throws UnknownHostException {
return InetAddress.getLocalHost().getHostName();
}
/**
* 获取系统首选IP
*
* @return
* @throws UnknownHostException
*/
public static String getLocalIP() throws UnknownHostException {
return InetAddress.getLocalHost().getHostAddress();
}
/**
* 获取所有网卡IP排除回文地址虚拟地址
*
* @return
* @throws SocketException
*/
public static String[] getLocalIPs() throws SocketException {
List<String> list = new ArrayList<>();
Enumeration<NetworkInterface> enumeration = NetworkInterface.getNetworkInterfaces();
while (enumeration.hasMoreElements()) {
NetworkInterface intf = enumeration.nextElement();
if (intf.isLoopback() || intf.isVirtual()) { //
continue;
}
Enumeration<InetAddress> inets = intf.getInetAddresses();
while (inets.hasMoreElements()) {
InetAddress addr = inets.nextElement();
if (addr.isLoopbackAddress() || !addr.isSiteLocalAddress() || addr.isAnyLocalAddress()) {
continue;
}
list.add(addr.getHostAddress());
}
}
return list.toArray(new String[0]);
}
/**
* 判断操作系统是否是Windows
*
* @return
*/
public static boolean isWindowsOS() {
boolean isWindowsOS = false;
String osName = System.getProperty("os.name");
if (osName.toLowerCase().contains("windows")) {
isWindowsOS = true;
}
return isWindowsOS;
}
}

View File

@ -0,0 +1,17 @@
package com.mingliqiye.disk.util;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import java.io.IOException;
public class NullUtil {
public static class NullJsonSerializer extends JsonSerializer<String> {
@Override
public void serialize(String value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
gen.writeString("***");
}
}
}

View File

@ -0,0 +1,23 @@
package com.mingliqiye.disk.util;
import cn.dev33.satoken.stp.StpUtil;
import com.mingliqiye.disk.uuid.UUID;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public class PanStpUtil {
public static UUID getLoginId() {
return UUID.ofString((String) StpUtil.getLoginId());
}
@Nullable
public static UUID getLoginIdDefaultNull() {
return UUID.ofString((String) StpUtil.getLoginId());
}
public static String login(@NotNull UUID uuid) {
StpUtil.login(uuid.toUUIDString());
return StpUtil.getTokenValue();
}
}

View File

@ -0,0 +1,66 @@
package com.mingliqiye.disk.util;
import com.mingliqiye.disk.function.P1R1Function;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.slf4j.helpers.MessageFormatter;
public class StringUtil {
public static String toString(Object obj) {
return obj == null ? "" : obj.toString();
}
public static String format(String format, Object... args) {
return MessageFormatter.arrayFormat(format, args).getMessage();
}
public static boolean isEmpty(String str) {
return str == null || str.isEmpty();
}
public static void main(String[] args) {
String d = joinOf(",", "", "", "1", "", "2", "3", "4", "5", "6", "7", "8", "9");
StringUtil.println(d);
List<String> dd = split(d, ",");
StringUtil.println(dd);
println(10000 / 1000);
}
public static String joinOf(String spec, String... objects) {
return join(spec, Arrays.asList(objects));
}
public static void println(Object... objects) {
if (objects.length == 1) {
System.out.println(objects[0]);
} else {
System.out.println(join(" ", ListUtil.toStringList(objects)));
}
}
public static List<String> split(String str, String separator) {
List<String> data = new ArrayList<>(Arrays.asList(str.split(separator)));
while (!data.isEmpty() && data.getFirst().isEmpty()) data.removeFirst();
return data;
}
public static <P> String join(String separator, List<P> list, P1R1Function<P, String> fun) {
StringBuilder sb = StringUtil.stringBuilder();
for (int i = 0; i < list.size(); i++) {
P item = list.get(i);
if (i == 0) sb.append(fun == null ? item.toString() : fun.call(item));
else sb.append(separator).append(fun == null ? item.toString() : fun.call(item));
}
return sb.toString();
}
public static String join(String separator, List<String> list) {
return join(separator, list, null);
}
public static StringBuilder stringBuilder() {
return new StringBuilder();
}
}

View File

@ -0,0 +1,40 @@
package com.mingliqiye.disk.util;
import lombok.Getter;
public class SystemUtil {
@Getter
private static final String osName = System.getProperties().getProperty("os.name");
public static boolean isWindows() {
return osName != null && osName.startsWith("Windows");
}
public static boolean isMac() {
return osName != null && osName.startsWith("Mac");
}
public static boolean isUnix() {
return ((osName != null && osName.startsWith("Linux")) || (!isWindows() && !isMac()));
}
public static String getJdkVersion() {
return System.getProperty("java.specification.version");
}
public static Integer getJavaVersionFloat() {
String version = getJdkVersion();
String uversion;
if (version.startsWith("1.")) {
uversion = version.substring(2, 3);
} else {
uversion = version.substring(0, 2);
}
return Integer.parseInt(uversion);
}
public static boolean isjdk8plus() {
return getJavaVersionFloat() > 8;
}
}

View File

@ -0,0 +1,82 @@
package com.mingliqiye.disk.util;
/**
* 线程局部变量工具类
*/
public class ThreadLocalDataHolderUtil {
// 静态实例用于存储字符串类型的线程局部变量
public static final ThreadLocalDataHolder<String> LibrarysLocalDataHolderStatic = new ThreadLocalDataHolder<>();
/**
* 泛型线程局部变量持有器
*
* @param <T> 存储的数据类型
*/
public static class ThreadLocalDataHolder<T> {
private final ThreadLocal<T> threadLocal;
public ThreadLocalDataHolder() {
this.threadLocal = new ThreadLocal<>();
}
/**
* 获取当前线程存储的值
*
* @return 当前线程存储的值如果没有则返回null
*/
public T get() {
return threadLocal.get();
}
/**
* 设置当前线程的值
*
* @param value 要存储的值
*/
public void set(T value) {
threadLocal.set(value);
}
/**
* 移除当前线程存储的值
*/
public void remove() {
threadLocal.remove();
}
/**
* 获取当前线程存储的值如果不存在则返回默认值
*
* @param defaultValue 默认值
* @return 当前线程存储的值或默认值
*/
public T getOrDefault(T defaultValue) {
T value = threadLocal.get();
return value != null ? value : defaultValue;
}
/**
* 安全获取值避免NPE
*
* @return 值或null
*/
public T safeGet() {
try {
return threadLocal.get();
} catch (Exception e) {
return null;
}
}
/**
* 检查当前线程是否有值
*
* @return 是否有值
*/
public boolean isPresent() {
return threadLocal.get() != null;
}
}
}

View File

@ -0,0 +1,141 @@
package com.mingliqiye.disk.uuid;
import com.fasterxml.jackson.annotation.JsonView;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.github.f4b6a3.uuid.UuidCreator;
import com.mingliqiye.disk.time.DateTime;
import com.mingliqiye.disk.util.StringUtil;
import io.swagger.v3.oas.annotations.media.Schema;
import java.io.Serializable;
import java.nio.ByteBuffer;
import java.util.Locale;
import java.util.Objects;
import lombok.Setter;
@Setter
@JsonSerialize(using = UUIDJsonSerializer.class)
@JsonDeserialize(using = UUIDJsonDeserializer.class)
@JsonView(String.class)
@Schema(type = "String")
public class UUID implements Serializable {
private java.util.UUID uuid;
public UUID(long msb, long lsb) {
uuid = new java.util.UUID(msb, lsb);
}
public UUID() {
uuid = UuidCreator.getTimeBased();
}
public UUID(java.util.UUID uuid) {
this.uuid = uuid;
}
public UUID(String uuid) {
this.uuid = java.util.UUID.fromString(uuid);
}
public static UUID of(byte[] bytes) {
ByteBuffer bb = ByteBuffer.wrap(bytes);
long msb = bb.getLong();
long lsb = bb.getLong();
return new UUID(msb, lsb);
}
public static UUID ofString(String data) {
try {
java.util.UUID uuid1 = java.util.UUID.fromString(data);
UUID uuid = new UUID();
uuid.setUuid(uuid1);
return uuid;
} catch (Exception e) {
throw new UUIDException(e.getMessage(), e);
}
}
public byte[] toBytes() {
ByteBuffer bb = ByteBuffer.wrap(new byte[16]);
bb.putLong(uuid.getMostSignificantBits());
bb.putLong(uuid.getLeastSignificantBits());
return bb.array();
}
public java.util.UUID GetUUID() {
return uuid;
}
public String toUUIDString() {
return toUUIDString(false);
}
public String toUUIDString(boolean u) {
if (uuid == null) {
throw new UUIDException("uuid is null : NullPointerException");
}
if (u) {
return uuid.toString().toUpperCase(Locale.ROOT);
}
return uuid.toString();
}
@Override
public int hashCode() {
return Objects.hash(uuid);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
UUID uuid = (UUID) o;
return Objects.equals(this.uuid, uuid.uuid);
}
@Override
public String toString() {
if (uuid == null) return "UUID(null)";
if (uuid.version() == 1) return StringUtil.format(
"UUID(uuid={},time={},mac={},version={})",
toUUIDString(true),
getDateTime().format(DateTime.Formats.STANDARD_DATETIME),
extractMACFromUUID(),
uuid.version()
);
return StringUtil.format("UUID(uuid={},version={})", toUUIDString(true), uuid.version());
}
public DateTime getDateTime() {
if (uuid == null) {
return null;
}
return DateTime.ofCurrentTimeMillis(uuid.timestamp() / 10_000).plusDays(-141427L);
}
public String extractMACFromUUID() {
return extractMACFromUUID(null);
}
public String extractMACFromUUID(String spec) {
if (uuid == null) {
throw new UUIDException("uuid is null : NullPointerException");
}
if (spec == null) {
spec = ":";
}
long leastSigBits = uuid.getLeastSignificantBits();
long macLong = leastSigBits & 0xFFFFFFFFFFFFL;
byte[] macBytes = new byte[6];
for (int i = 0; i < 6; i++) {
macBytes[5 - i] = (byte) (macLong >> (8 * i));
}
StringBuilder mac = new StringBuilder();
for (int i = 0; i < 6; i++) {
mac.append(String.format("%02X", macBytes[i]));
if (i < 5) mac.append(spec);
}
return mac.toString();
}
}

View File

@ -0,0 +1,12 @@
package com.mingliqiye.disk.uuid;
public class UUIDException extends RuntimeException {
public UUIDException(String message) {
super(message);
}
public UUIDException(String message, Throwable cause) {
super(message, cause);
}
}

View File

@ -0,0 +1,17 @@
package com.mingliqiye.disk.uuid;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import java.io.IOException;
public class UUIDJsonDeserializer extends JsonDeserializer<UUID> {
@Override
public UUID deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
if (p.isNaN()) {
return null;
}
return new UUID(p.getValueAsString());
}
}

View File

@ -0,0 +1,34 @@
package com.mingliqiye.disk.uuid;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonToken;
import com.fasterxml.jackson.core.type.WritableTypeId;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.jsontype.TypeSerializer;
import java.io.IOException;
public class UUIDJsonSerializer extends JsonSerializer<UUID> {
@Override
public void serialize(UUID uuid, JsonGenerator jsonGenerator, SerializerProvider serializerProvider)
throws UUIDException, IOException {
if (uuid == null) {
jsonGenerator.writeNull();
return;
}
jsonGenerator.writeString(uuid.toUUIDString());
}
@Override
public void serializeWithType(
UUID value,
JsonGenerator gen,
SerializerProvider serializers,
TypeSerializer typeSer
) throws IOException {
WritableTypeId typeId = typeSer.writeTypePrefix(gen, typeSer.typeId(value, JsonToken.VALUE_STRING));
serialize(value, gen, serializers);
typeSer.writeTypeSuffix(gen, typeId);
}
}

View File

@ -0,0 +1,90 @@
config:
port: 9963
app-name: pan-disk
app-version: 1.1
data-source:
mysql:
username: pan
password: PhXCRiCfEmiGesEm
host: 10.0.0.4
port: 3307
database: pan
redis:
host: 10.0.0.4
port: 6380
database: 0
username: ''
password: redis_kTJpsa
spring:
application:
name: ${config.app-name}
version: ${config.app-version}
banner:
location: classpath:banner/banner.txt
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://${config.data-source.mysql.host}:${config.data-source.mysql.port}/${config.data-source.mysql.database}?rewriteBatchedStatements=true
username: ${config.data-source.mysql.username}
password: ${config.data-source.mysql.password}
type: com.alibaba.druid.pool.DruidDataSource
druid:
initial-size: 0
min-idle: 0
maxActive: 1024
connect-timeout: 1000
data:
redis:
host: ${config.data-source.redis.host}
port: ${config.data-source.redis.port}
database: ${config.data-source.redis.database}
password: ${config.data-source.redis.password}
username: ${config.data-source.redis.username}
connect-timeout: 5s
timeout: 5s
lettuce:
pool:
min-idle: 0
max-idle: 16
max-active: 16
max-wait: 5s
mvc:
static-path-pattern: /static/**
web:
resources:
static-locations: classpath:/html/static
server:
port: ${config.port}
sa-token:
token-name: Authorization
timeout: 2592000
active-timeout: -1
is-concurrent: true
is-share: false
token-style: random-128
is-log: false
jwt-secret-key: pzmqljgvdyidabacksmoaghgyikxoasckjhgyuhij
token-prefix: Bearer
is-read-body: false
is-read-cookie: false
mybatis-plus:
type-handlers-package: com.mingliqiye.disk.typeHandlers
configuration:
cache-enabled: true
global-config:
db-config:
id-type: auto
springdoc:
swagger-ui:
enabled: false
api-docs:
path: /apis/swagger/api.json

View File

@ -0,0 +1,6 @@
__ __ _____ _ _ _____ _ _____ ____ _____ __ __ ______
| \/ | |_ _| | \ | | / ____| | | |_ _| / __ \ |_ _| \ \ / / | ____|
| \ / | | | | \| | | | __ | | | | | | | | | | \ \_/ / | |__
| |\/| | | | | . ` | | | |_ | | | | | | | | | | | \ / | __|
| | | | _| |_ | |\ | | |__| | | |____ _| |_ | |__| | _| |_ | | | |____
|_| |_| |_____| |_| \_| \_____| |______| |_____| \___\_\ |_____| |_| |______|

View File

@ -0,0 +1,29 @@
Appenders:
Console: #输出到控制台
name: CONSOLE #Appender命名
target: SYSTEM_OUT
PatternLayout:
pattern: "%style{%d{yyyy-MM-dd HH:mm:ss,SSS}}{bright,magenta} [%highlight{%p}{FATAL=white, ERROR=bright_red, WARN=bright_yellow, INFO=bright_green, DEBUG=bright_cyan, TRACE=bright_blue}] [%style{%t}{bright,yellow}/%style{%c{1}}{bright,cyan}] -- %style{%m}{#EEDFCC}%n" #输出日志的格式
disableAnsi: "${env:DISABLECOLOR:-false}"
RollingFile:
- name: FILE
fileName: 'log/info.log'
filePattern: "log/$${date:yyyy-MM-dd}/%d{yyyy-MM-dd}-%i.log"
PatternLayout:
pattern: "%d{yyyy-MM-dd HH:mm:ss,SSS} [%p] [%t/%F:%L/%M/%c] -- %m%n" #输出日志的格式
Policies:
SizeBasedTriggeringPolicy:
size: "10 KB"
TimeBasedTriggeringPolicy:
modulate: true
interval: 1
DefaultRolloverStrategy: # 单目录下文件最多20个超过会删除最早之前的
max: 1000
Loggers:
Root:
level: INFO
additivity: true
AppenderRef:
- ref: CONSOLE
- ref: FILE

View File

@ -0,0 +1,3 @@
create database meven_repository collate utf8mb4_unicode_ci;
create user 'meven_repository_admin'@'%' IDENTIFIED BY 'meven_repository_admin_password';
GRANT ALL ON meven_repository.* TO 'meven_repository_admin'@'%'

View File

@ -0,0 +1,28 @@
DROP TABLE IF EXISTS `users`;
create table `users`
(
id binary(16) default (uuid_to_bin(
uuid(),
1)) primary key,
username varchar(256) unique not null,
password varchar(128) not null,
nickname varchar(256) not null,
prermissions json,
roles json,
icon mediumblob,
admin bool default false,
create_user_id binary(16) not null,
create_time timestamp(6) default now(6) not null,
update_user_id binary(16) null,
update_time timestamp(6) default now(6),
constraint foreign key (create_user_id) references users (id),
constraint foreign key (update_user_id) references users (id)
);
INSERT INTO users (id, username, password, nickname, prermissions, roles, icon, admin, create_user_id)
VALUES (0x48126760548311F0B90C6D1CD960F6F3, 'system', 'system', '系统', '[]', '[]', null, 1,
0x48126760548311F0B90C6D1CD960F6F3);
INSERT INTO users (id, username, password, nickname, prermissions, roles, icon, admin, create_user_id)
VALUES (0x689EFC204D1F11F08134DB0063E177A7, 'admin', '$2a$10$ti.W.FpQobTaW7eTTmHYJOuBS68uPP0Ikkw33UTCJSgcNhg0AGy7a',
'管理员', '[]', '[]', null, 1,
0x48126760548311F0B90C6D1CD960F6F3);

View File

@ -1,11 +1,26 @@
package com.mingliqiye.disk;
import com.mingliqiye.disk.mappers.UserMapper;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import org.mindrot.jbcrypt.BCrypt;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
@Slf4j
@SpringBootTest
class DiskApplicationTests {
@Autowired
UserMapper userMapper;
@Test
void contextLoads() {}
void contextLoads() {
log.info("{}", userMapper.selectList(null));
}
@Test
void getBCryptPdw() {
log.info("{}", BCrypt.hashpw("admin", BCrypt.gensalt()));
}
}

View File

@ -1,12 +1,20 @@
{
"extends": "@vue/tsconfig/tsconfig.dom.json",
"include": ["env.d.ts", "web-src/**/*", "web-src/**/*.vue"],
"exclude": ["web-src/**/__tests__/*"],
"include": [
"env.d.ts",
"web-src/**/*",
"web-src/**/*.vue",
"web-src/types/**/*.d.ts"
],
"exclude": [
"web-src/**/__tests__/*"
],
"compilerOptions": {
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
"paths": {
"@/*": ["./web-src/*"]
"@/*": [
"./web-src/*"
]
}
}
}

View File

@ -2,25 +2,53 @@ import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
import vueDevTools from 'vite-plugin-vue-devtools';
import path from 'node:path';
import AutoImport from 'unplugin-auto-import/vite';
import { NaiveUiResolver } from 'unplugin-vue-components/resolvers';
import Components from 'unplugin-vue-components/vite';
// https://vite.dev/config/
export default defineConfig({
root: path.resolve(__dirname, 'web-src'),
plugins: [vue(), vueDevTools()],
plugins: [
vue(),
vueDevTools(),
AutoImport({
imports: [
'vue',
'@vueuse/core',
'pinia',
'vue-router',
{
'naive-ui': ['useDialog', 'useMessage', 'useNotification', 'useLoadingBar'],
},
],
dts: 'types/AutoImport.d.ts',
}),
Components({
resolvers: [NaiveUiResolver()],
dts: 'types/Components.d.ts',
}),
],
resolve: {
alias: {
'@': path.resolve(__dirname, 'web-src'),
},
},
build: {
outDir: path.resolve(__dirname, 'src/main/resources/static'),
outDir: path.resolve(__dirname, 'src/main/resources/html'),
emptyOutDir: true,
rollupOptions: {
output: {
chunkFileNames: 'js/[hash].js',
entryFileNames: 'js/[hash].js',
assetFileNames: '[ext]/[hash].[ext]',
chunkFileNames: 'static/js/[hash].js',
entryFileNames: 'static/js/[hash].js',
assetFileNames: 'static/[ext]/[hash].[ext]',
},
},
},
server: {
host: '0.0.0.0',
port: 5174,
proxy: {
'/apis': 'http://localhost:9963',
},
},
});

View File

@ -1,85 +1,25 @@
<script setup lang="ts">
import { RouterLink, RouterView } from 'vue-router';
import HelloWorld from './components/HelloWorld.vue';
</script>
<template>
<header>
<img alt="Vue logo" class="logo" src="@/assets/logo.svg" width="125" height="125" />
<div class="wrapper">
<HelloWorld msg="You did it!" />
<nav>
<RouterLink to="/">Home</RouterLink>
<RouterLink to="/about">About</RouterLink>
</nav>
</div>
</header>
<RouterView />
<n-config-provider :hljs="hljs" :theme="useSettingStore.theme === 'dark' ? darkTheme : lightTheme" abstract>
<n-loading-bar-provider>
<n-message-provider>
<Index />
</n-message-provider>
</n-loading-bar-provider>
</n-config-provider>
</template>
<style scoped>
header {
line-height: 1.5;
max-height: 100vh;
}
<script lang="ts" setup>
import Index from '@/layout/index.vue';
import { darkTheme, lightTheme } from 'naive-ui';
import hljs from 'highlight.js/lib/core';
import javascript from 'highlight.js/lib/languages/javascript';
import typescript from 'highlight.js/lib/languages/typescript';
import json from 'highlight.js/lib/languages/json';
import { UseSettingStore } from '@/plugin';
.logo {
display: block;
margin: 0 auto 2rem;
}
hljs.registerLanguage('json', json);
hljs.registerLanguage('typescript', typescript);
hljs.registerLanguage('javascript', javascript);
nav {
width: 100%;
font-size: 12px;
text-align: center;
margin-top: 2rem;
}
nav a.router-link-exact-active {
color: var(--color-text);
}
nav a.router-link-exact-active:hover {
background-color: transparent;
}
nav a {
display: inline-block;
padding: 0 1rem;
border-left: 1px solid var(--color-border);
}
nav a:first-of-type {
border: 0;
}
@media (min-width: 1024px) {
header {
display: flex;
place-items: center;
padding-right: calc(var(--section-gap) / 2);
}
.logo {
margin: 0 2rem 0 0;
}
header .wrapper {
display: flex;
place-items: flex-start;
flex-wrap: wrap;
}
nav {
text-align: left;
margin-left: -1rem;
font-size: 1rem;
padding: 1rem 0;
margin-top: 1rem;
}
}
</style>
const useSettingStore = UseSettingStore();
</script>

33
web-src/api/auth.ts Normal file
View File

@ -0,0 +1,33 @@
import { alovaInstance, type AlovaResponseType } from '@/plugin';
export function login(data: loginDtoBody) {
return alovaInstance.Post<AlovaResponseType<string>>('/apis/auth/login', data);
}
export function woIsMe() {
return alovaInstance.Get<AlovaResponseType<UserDto>>('apis/auth/who-is-me');
}
export function logout() {
return alovaInstance.Delete<AlovaResponseType>('apis/auth/logout');
}
export interface loginDtoBody {
username: string;
password: string;
}
export interface UserDto {
id: string;
username: string;
password: string;
nickname: string;
prermissions: string[];
roles: string[];
icon: string;
admin: boolean;
createUserId: string;
createTime: string;
updateUserId: string;
updateTime: string;
}

1
web-src/api/index.ts Normal file
View File

@ -0,0 +1 @@
export * from './auth';

10
web-src/api/system.ts Normal file
View File

@ -0,0 +1,10 @@
import { alovaInstance, type AlovaResponseType } from '@/plugin';
export function getSysInfo() {
return alovaInstance.Get<AlovaResponseType<SysInfo>>('apis/system/info');
}
export interface SysInfo {
appName: string;
appVersion: string;
}

File diff suppressed because it is too large Load Diff

View File

@ -1,86 +0,0 @@
/* color palette from <https://github.com/vuejs/theme> */
:root {
--vt-c-white: #ffffff;
--vt-c-white-soft: #f8f8f8;
--vt-c-white-mute: #f2f2f2;
--vt-c-black: #181818;
--vt-c-black-soft: #222222;
--vt-c-black-mute: #282828;
--vt-c-indigo: #2c3e50;
--vt-c-divider-light-1: rgba(60, 60, 60, 0.29);
--vt-c-divider-light-2: rgba(60, 60, 60, 0.12);
--vt-c-divider-dark-1: rgba(84, 84, 84, 0.65);
--vt-c-divider-dark-2: rgba(84, 84, 84, 0.48);
--vt-c-text-light-1: var(--vt-c-indigo);
--vt-c-text-light-2: rgba(60, 60, 60, 0.66);
--vt-c-text-dark-1: var(--vt-c-white);
--vt-c-text-dark-2: rgba(235, 235, 235, 0.64);
}
/* semantic color variables for this project */
:root {
--color-background: var(--vt-c-white);
--color-background-soft: var(--vt-c-white-soft);
--color-background-mute: var(--vt-c-white-mute);
--color-border: var(--vt-c-divider-light-2);
--color-border-hover: var(--vt-c-divider-light-1);
--color-heading: var(--vt-c-text-light-1);
--color-text: var(--vt-c-text-light-1);
--section-gap: 160px;
}
@media (prefers-color-scheme: dark) {
:root {
--color-background: var(--vt-c-black);
--color-background-soft: var(--vt-c-black-soft);
--color-background-mute: var(--vt-c-black-mute);
--color-border: var(--vt-c-divider-dark-2);
--color-border-hover: var(--vt-c-divider-dark-1);
--color-heading: var(--vt-c-text-dark-1);
--color-text: var(--vt-c-text-dark-2);
}
}
*,
*::before,
*::after {
box-sizing: border-box;
margin: 0;
font-weight: normal;
}
body {
min-height: 100vh;
color: var(--color-text);
background: var(--color-background);
transition:
color 0.5s,
background-color 0.5s;
line-height: 1.6;
font-family:
Inter,
-apple-system,
BlinkMacSystemFont,
'Segoe UI',
Roboto,
Oxygen,
Ubuntu,
Cantarell,
'Fira Sans',
'Droid Sans',
'Helvetica Neue',
sans-serif;
font-size: 15px;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}

17
web-src/assets/index.sass Normal file
View File

@ -0,0 +1,17 @@
:where(a)
color: #4990e2
text-decoration: none
.n-collapse-item__header
padding-top: 0 !important
.n-collapse-item__header-main
padding-left: 10px !important
.n-collapse-item__content-wrapper
border-top: 1px solid var(--color)
html
scroll-padding-top: 70px
scroll-behavior: smooth

143
web-src/assets/index.scss Normal file
View File

@ -0,0 +1,143 @@
@use "var";
:where(*) {
padding: 0;
margin: 0;
border: 0;
transition: background-color 0.3s ease;
}
html {
color: #3c3c3c;
--text-color: 60, 60, 60;
font-family: "Helvetica Neue", Helvetica, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "微软雅黑", Arial, sans-serif;
}
html.dark {
color: #d5d5d5;
--text-color: 213, 213, 213;
}
.swagger-ui .info {
margin: 0 !important;
}
::-webkit-scrollbar {
width: 14px;
height: 14px;
}
::-webkit-scrollbar-button {
background-color: #8f8f8f !important;
}
::-webkit-scrollbar-track {
background-color: #f5f5f5 !important;
}
::-webkit-scrollbar-track-piece {
background-color: #e0e0e0 !important;
}
::-webkit-scrollbar-thumb {
height: 50px;
background-color: #bdbdbd !important;
border: 2px solid #e0e0e0 !important;
}
::-webkit-scrollbar-button:vertical:start:decrement {
background: linear-gradient(130deg, #cccccc 40%, transparent 41%),
linear-gradient(230deg, #cccccc 40%, transparent 41%),
linear-gradient(0deg, #cccccc 40%, transparent 31%);
background-color: #cccccc;
}
::-webkit-scrollbar-button:vertical:end:increment {
background: linear-gradient(310deg, #cccccc 40%, transparent 41%),
linear-gradient(50deg, #cccccc 40%, transparent 41%),
linear-gradient(180deg, #cccccc 40%, transparent 31%);
background-color: #cccccc;
}
::-webkit-scrollbar-button:horizontal:end:increment {
background: linear-gradient(210deg, #9e9e9e 40%, transparent 41%),
linear-gradient(330deg, #9e9e9e 40%, transparent 41%),
linear-gradient(90deg, #9e9e9e 30%, transparent 31%);
background-color: #ffffff;
}
::-webkit-scrollbar-button:horizontal:start:decrement {
background: linear-gradient(30deg, #9e9e9e 40%, transparent 41%),
linear-gradient(150deg, #9e9e9e 40%, transparent 41%),
linear-gradient(270deg, #9e9e9e 30%, transparent 31%);
background-color: #ffffff;
}
html.dark {
:where(body) {
background: #202020;
}
::-webkit-scrollbar {
width: 14px;
height: 14px;
}
::-webkit-scrollbar-button {
background-color: #3e4346 !important;
}
::-webkit-scrollbar-track {
background-color: #646464 !important;
}
::-webkit-scrollbar-track-piece {
background-color: #3e4346 !important;
}
::-webkit-scrollbar-thumb {
height: 50px;
background-color: #242424 !important;
border: 2px solid #3e4346 !important;
}
::-webkit-scrollbar-button:vertical:start:decrement {
background: linear-gradient(130deg, #696969 40%, rgba(255, 0, 0, 0) 41%),
linear-gradient(230deg, #696969 40%, rgba(0, 0, 0, 0) 41%),
linear-gradient(0deg, #696969 40%, rgba(0, 0, 0, 0) 31%);
background-color: #b6b6b6;
}
::-webkit-scrollbar-button:vertical:end:increment {
background: linear-gradient(310deg, #696969 40%, rgba(0, 0, 0, 0) 41%),
linear-gradient(50deg, #696969 40%, rgba(0, 0, 0, 0) 41%),
linear-gradient(180deg, #696969 40%, rgba(0, 0, 0, 0) 31%);
background-color: #b6b6b6;
}
::-webkit-scrollbar-button:horizontal:end:increment {
background: linear-gradient(210deg, #696969 40%, rgba(0, 0, 0, 0) 41%),
linear-gradient(330deg, #696969 40%, rgba(0, 0, 0, 0) 41%),
linear-gradient(90deg, #696969 30%, rgba(0, 0, 0, 0) 31%);
background-color: #b6b6b6;
}
::-webkit-scrollbar-button:horizontal:start:decrement {
background: linear-gradient(30deg, #696969 40%, rgba(0, 0, 0, 0) 41%),
linear-gradient(150deg, #696969 40%, rgba(0, 0, 0, 0) 41%),
linear-gradient(270deg, #696969 30%, rgba(0, 0, 0, 0) 31%);
background-color: #b6b6b6;
}
}
.n-drawer-mask {
backdrop-filter: blur(2px);
}
.n-message {
padding: 8px;
}

9
web-src/assets/index.svg Normal file
View File

@ -0,0 +1,9 @@
<svg viewBox="0 0 1650 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"
data-spm-anchor-id="a313x.search_index.0.i10.3b363a81fRIAyG" width="256" height="256">
<path d="M1368.639733 314.94553a283.692506 283.692506 0 0 0-45.17515 3.532214 436.693118 436.693118 0 0 0-282.577071-295.032771 448.219288 448.219288 0 0 0-407.691787 63.022123 38.854347 38.854347 0 0 0-7.993956 55.771791 40.713407 40.713407 0 0 0 55.77179 7.80805 366.792474 366.792474 0 0 1 350.618655-46.290586 356.009928 356.009928 0 0 1 222.343537 269.563653 39.412065 39.412065 0 0 0 18.590597 27.142272 40.713407 40.713407 0 0 0 33.277168 3.718119 201.707975 201.707975 0 0 1 254.877081 122.697938 196.130796 196.130796 0 0 1-123.813374 249.671715 39.226159 39.226159 0 0 0-24.539588 50.194611 40.341595 40.341595 0 0 0 51.124141 23.98187 275.140832 275.140832 0 0 0 183.303284-306.558941 278.858951 278.858951 0 0 0-276.999891-229.036152z"
fill="#9aed7f"/>
<path d="M1368.639733 787.704406a238.703262 238.703262 0 0 1-240.748228 236.286485H240.934134a236.379438 236.379438 0 1 1 0-472.758876 151.69927 151.69927 0 0 1 22.494622 1.115436 402.858232 402.858232 0 0 1 788.984926 10.782546 244.652253 244.652253 0 0 1 217.13817 33.277168 234.985143 234.985143 0 0 1 99.831505 191.483147z"
fill="#9aed7f"/>
<path d="M1100.377422 632.472923a156.532825 156.532825 0 0 0-23.05234 5.763085l-43.873809 13.942948a40.713407 40.713407 0 0 1-33.277168-3.71812 39.412065 39.412065 0 0 1-18.590596-27.142271L973.589552 576.329321a322.175042 322.175042 0 0 0-632.08029-8.737581l-7.064426 34.764416a39.969783 39.969783 0 0 1-42.386561 31.232203l-35.879852-3.346308h-12.827512a157.64826 157.64826 0 1 0-2.974495 315.110615h3.346307l81.61272-1.301341v1.301341h799.395661a161.552286 161.552286 0 0 0 163.411345-151.141551 156.161013 156.161013 0 0 0-54.470448-125.114716 162.667722 162.667722 0 0 0-133.480485-37.181194z"
fill="#cdf8bf"/>
</svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 261.76 226.69"><path d="M161.096.001l-30.225 52.351L100.647.001H-.005l130.877 226.688L261.749.001z" fill="#41b883"/><path d="M161.096.001l-30.225 52.351L100.647.001H52.346l78.526 136.01L209.398.001z" fill="#34495e"/></svg>

Before

Width:  |  Height:  |  Size: 276 B

View File

@ -1,35 +0,0 @@
@import './base.css';
#app {
max-width: 1280px;
margin: 0 auto;
padding: 2rem;
font-weight: normal;
}
a,
.green {
text-decoration: none;
color: hsla(160, 100%, 37%, 1);
transition: 0.4s;
padding: 3px;
}
@media (hover: hover) {
a:hover {
background-color: hsla(160, 100%, 37%, 0.2);
}
}
@media (min-width: 1024px) {
body {
display: flex;
place-items: center;
}
#app {
display: grid;
grid-template-columns: 1fr 1fr;
padding: 0 2rem;
}
}

18
web-src/assets/var.scss Normal file
View File

@ -0,0 +1,18 @@
*[n-c] {
align-items: center;
}
*[c-n] {
justify-content: center;
}
*[c-c] {
justify-content: center;
align-items: center;
}
*[f] {
display: flex;
}

View File

@ -1,43 +0,0 @@
<script setup lang="ts">
defineProps<{
msg: string;
}>();
</script>
<template>
<div class="greetings">
<h1 class="green">{{ msg }}</h1>
<h3>
Youve successfully created a project with
<a href="https://vite.dev/" target="_blank" rel="noopener">Vite</a>
+
<a href="https://vuejs.org/" target="_blank" rel="noopener">Vue 3</a>
. What's next?
</h3>
</div>
</template>
<style scoped>
h1 {
font-weight: 500;
font-size: 2.6rem;
position: relative;
top: -10px;
}
h3 {
font-size: 1.2rem;
}
.greetings h1,
.greetings h3 {
text-align: center;
}
@media (min-width: 1024px) {
.greetings h1,
.greetings h3 {
text-align: left;
}
}
</style>

View File

@ -0,0 +1,41 @@
<template>
<div class="iconstyle">
<Icon :icon="icon" />
</div>
</template>
<script lang="ts" setup>
import { Icon } from '@iconify/vue';
defineProps({
icon: {
type: String,
required: true,
},
color: {
type: String,
required: false,
default: 'inherit',
},
size: {
type: String,
required: false,
default: '24px',
},
bSize: {
type: String,
required: false,
default: '24px',
},
});
</script>
<style lang="scss" scoped>
.iconstyle {
width: v-bind(bSize);
height: v-bind(bSize);
display: flex;
align-items: center;
justify-content: center;
font-size: v-bind(size);
color: v-bind(color);
}
</style>

View File

@ -0,0 +1,21 @@
<template>
<n-code :code="recode" :language="language" show-line-numbers style="width: fit-content !important; padding: 10px" />
</template>
<script lang="ts" setup>
const recode = computed(() => {
if (typeof prop.code === 'object') {
return JSON.stringify(prop.code, null, 2);
} else {
return prop.code;
}
});
const prop = defineProps({
code: {
type: [String, Object],
required: true,
},
language: String,
});
</script>
<style lang="scss" scoped></style>

View File

@ -1,97 +0,0 @@
<script setup lang="ts">
import WelcomeItem from './WelcomeItem.vue';
import DocumentationIcon from './icons/IconDocumentation.vue';
import ToolingIcon from './icons/IconTooling.vue';
import EcosystemIcon from './icons/IconEcosystem.vue';
import CommunityIcon from './icons/IconCommunity.vue';
import SupportIcon from './icons/IconSupport.vue';
const openReadmeInEditor = () => fetch('/__open-in-editor?file=README.md');
</script>
<template>
<WelcomeItem>
<template #icon>
<DocumentationIcon />
</template>
<template #heading>Documentation</template>
Vues
<a href="https://vuejs.org/" target="_blank" rel="noopener">official documentation</a>
provides you with all information you need to get started.
</WelcomeItem>
<WelcomeItem>
<template #icon>
<ToolingIcon />
</template>
<template #heading>Tooling</template>
This project is served and bundled with
<a href="https://vite.dev/guide/features.html" target="_blank" rel="noopener">Vite</a>
. The recommended IDE setup is
<a href="https://code.visualstudio.com/" target="_blank" rel="noopener">VSCode</a>
+
<a href="https://github.com/vuejs/language-tools" target="_blank" rel="noopener">Vue - Official</a>
. If you need to test your components and web pages, check out
<a href="https://vitest.dev/" target="_blank" rel="noopener">Vitest</a>
and
<a href="https://www.cypress.io/" target="_blank" rel="noopener">Cypress</a>
/
<a href="https://playwright.dev/" target="_blank" rel="noopener">Playwright</a>
.
<br />
More instructions are available in
<a href="javascript:void(0)" @click="openReadmeInEditor"><code>README.md</code></a>
.
</WelcomeItem>
<WelcomeItem>
<template #icon>
<EcosystemIcon />
</template>
<template #heading>Ecosystem</template>
Get official tools and libraries for your project:
<a href="https://pinia.vuejs.org/" target="_blank" rel="noopener">Pinia</a>
,
<a href="https://router.vuejs.org/" target="_blank" rel="noopener">Vue Router</a>
,
<a href="https://test-utils.vuejs.org/" target="_blank" rel="noopener">Vue Test Utils</a>
, and
<a href="https://github.com/vuejs/devtools" target="_blank" rel="noopener">Vue Dev Tools</a>
. If you need more resources, we suggest paying
<a href="https://github.com/vuejs/awesome-vue" target="_blank" rel="noopener">Awesome Vue</a>
a visit.
</WelcomeItem>
<WelcomeItem>
<template #icon>
<CommunityIcon />
</template>
<template #heading>Community</template>
Got stuck? Ask your question on
<a href="https://chat.vuejs.org" target="_blank" rel="noopener">Vue Land</a>
(our official Discord server), or
<a href="https://stackoverflow.com/questions/tagged/vue.js" target="_blank" rel="noopener">StackOverflow</a>
. You should also follow the official
<a href="https://bsky.app/profile/vuejs.org" target="_blank" rel="noopener">@vuejs.org</a>
Bluesky account or the
<a href="https://x.com/vuejs" target="_blank" rel="noopener">@vuejs</a>
X account for latest news in the Vue world.
</WelcomeItem>
<WelcomeItem>
<template #icon>
<SupportIcon />
</template>
<template #heading>Support Vue</template>
As an independent project, Vue relies on community backing for its sustainability. You can help us by
<a href="https://vuejs.org/sponsor/" target="_blank" rel="noopener">becoming a sponsor</a>
.
</WelcomeItem>
</template>

View File

@ -1,87 +0,0 @@
<template>
<div class="item">
<i>
<slot name="icon"></slot>
</i>
<div class="details">
<h3>
<slot name="heading"></slot>
</h3>
<slot></slot>
</div>
</div>
</template>
<style scoped>
.item {
margin-top: 2rem;
display: flex;
position: relative;
}
.details {
flex: 1;
margin-left: 1rem;
}
i {
display: flex;
place-items: center;
place-content: center;
width: 32px;
height: 32px;
color: var(--color-text);
}
h3 {
font-size: 1.2rem;
font-weight: 500;
margin-bottom: 0.4rem;
color: var(--color-heading);
}
@media (min-width: 1024px) {
.item {
margin-top: 0;
padding: 0.4rem 0 1rem calc(var(--section-gap) / 2);
}
i {
top: calc(50% - 25px);
left: -26px;
position: absolute;
border: 1px solid var(--color-border);
background: var(--color-background);
border-radius: 8px;
width: 50px;
height: 50px;
}
.item:before {
content: ' ';
border-left: 1px solid var(--color-border);
position: absolute;
left: 0;
bottom: calc(50% + 25px);
height: calc(50% - 25px);
}
.item:after {
content: ' ';
border-left: 1px solid var(--color-border);
position: absolute;
left: 0;
top: calc(50% + 25px);
height: calc(50% - 25px);
}
.item:first-of-type:before {
display: none;
}
.item:last-of-type:after {
display: none;
}
}
</style>

View File

@ -1,6 +0,0 @@
<template>
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor">
<path
d="M15 4a1 1 0 1 0 0 2V4zm0 11v-1a1 1 0 0 0-1 1h1zm0 4l-.707.707A1 1 0 0 0 16 19h-1zm-4-4l.707-.707A1 1 0 0 0 11 14v1zm-4.707-1.293a1 1 0 0 0-1.414 1.414l1.414-1.414zm-.707.707l-.707-.707.707.707zM9 11v-1a1 1 0 0 0-.707.293L9 11zm-4 0h1a1 1 0 0 0-1-1v1zm0 4H4a1 1 0 0 0 1.707.707L5 15zm10-9h2V4h-2v2zm2 0a1 1 0 0 1 1 1h2a3 3 0 0 0-3-3v2zm1 1v6h2V7h-2zm0 6a1 1 0 0 1-1 1v2a3 3 0 0 0 3-3h-2zm-1 1h-2v2h2v-2zm-3 1v4h2v-4h-2zm1.707 3.293l-4-4-1.414 1.414 4 4 1.414-1.414zM11 14H7v2h4v-2zm-4 0c-.276 0-.525-.111-.707-.293l-1.414 1.414C5.42 15.663 6.172 16 7 16v-2zm-.707 1.121l3.414-3.414-1.414-1.414-3.414 3.414 1.414 1.414zM9 12h4v-2H9v2zm4 0a3 3 0 0 0 3-3h-2a1 1 0 0 1-1 1v2zm3-3V3h-2v6h2zm0-6a3 3 0 0 0-3-3v2a1 1 0 0 1 1 1h2zm-3-3H3v2h10V0zM3 0a3 3 0 0 0-3 3h2a1 1 0 0 1 1-1V0zM0 3v6h2V3H0zm0 6a3 3 0 0 0 3 3v-2a1 1 0 0 1-1-1H0zm3 3h2v-2H3v2zm1-1v4h2v-4H4zm1.707 4.707l.586-.586-1.414-1.414-.586.586 1.414 1.414z" />
</svg>
</template>

View File

@ -1,6 +0,0 @@
<template>
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="17" fill="currentColor">
<path
d="M11 2.253a1 1 0 1 0-2 0h2zm-2 13a1 1 0 1 0 2 0H9zm.447-12.167a1 1 0 1 0 1.107-1.666L9.447 3.086zM1 2.253L.447 1.42A1 1 0 0 0 0 2.253h1zm0 13H0a1 1 0 0 0 1.553.833L1 15.253zm8.447.833a1 1 0 1 0 1.107-1.666l-1.107 1.666zm0-14.666a1 1 0 1 0 1.107 1.666L9.447 1.42zM19 2.253h1a1 1 0 0 0-.447-.833L19 2.253zm0 13l-.553.833A1 1 0 0 0 20 15.253h-1zm-9.553-.833a1 1 0 1 0 1.107 1.666L9.447 14.42zM9 2.253v13h2v-13H9zm1.553-.833C9.203.523 7.42 0 5.5 0v2c1.572 0 2.961.431 3.947 1.086l1.107-1.666zM5.5 0C3.58 0 1.797.523.447 1.42l1.107 1.666C2.539 2.431 3.928 2 5.5 2V0zM0 2.253v13h2v-13H0zm1.553 13.833C2.539 15.431 3.928 15 5.5 15v-2c-1.92 0-3.703.523-5.053 1.42l1.107 1.666zM5.5 15c1.572 0 2.961.431 3.947 1.086l1.107-1.666C9.203 13.523 7.42 13 5.5 13v2zm5.053-11.914C11.539 2.431 12.928 2 14.5 2V0c-1.92 0-3.703.523-5.053 1.42l1.107 1.666zM14.5 2c1.573 0 2.961.431 3.947 1.086l1.107-1.666C18.203.523 16.421 0 14.5 0v2zm3.5.253v13h2v-13h-2zm1.553 12.167C18.203 13.523 16.421 13 14.5 13v2c1.573 0 2.961.431 3.947 1.086l1.107-1.666zM14.5 13c-1.92 0-3.703.523-5.053 1.42l1.107 1.666C11.539 15.431 12.928 15 14.5 15v-2z" />
</svg>
</template>

View File

@ -1,6 +0,0 @@
<template>
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="20" fill="currentColor">
<path
d="M11.447 8.894a1 1 0 1 0-.894-1.789l.894 1.789zm-2.894-.789a1 1 0 1 0 .894 1.789l-.894-1.789zm0 1.789a1 1 0 1 0 .894-1.789l-.894 1.789zM7.447 7.106a1 1 0 1 0-.894 1.789l.894-1.789zM10 9a1 1 0 1 0-2 0h2zm-2 2.5a1 1 0 1 0 2 0H8zm9.447-5.606a1 1 0 1 0-.894-1.789l.894 1.789zm-2.894-.789a1 1 0 1 0 .894 1.789l-.894-1.789zm2 .789a1 1 0 1 0 .894-1.789l-.894 1.789zm-1.106-2.789a1 1 0 1 0-.894 1.789l.894-1.789zM18 5a1 1 0 1 0-2 0h2zm-2 2.5a1 1 0 1 0 2 0h-2zm-5.447-4.606a1 1 0 1 0 .894-1.789l-.894 1.789zM9 1l.447-.894a1 1 0 0 0-.894 0L9 1zm-2.447.106a1 1 0 1 0 .894 1.789l-.894-1.789zm-6 3a1 1 0 1 0 .894 1.789L.553 4.106zm2.894.789a1 1 0 1 0-.894-1.789l.894 1.789zm-2-.789a1 1 0 1 0-.894 1.789l.894-1.789zm1.106 2.789a1 1 0 1 0 .894-1.789l-.894 1.789zM2 5a1 1 0 1 0-2 0h2zM0 7.5a1 1 0 1 0 2 0H0zm8.553 12.394a1 1 0 1 0 .894-1.789l-.894 1.789zm-1.106-2.789a1 1 0 1 0-.894 1.789l.894-1.789zm1.106 1a1 1 0 1 0 .894 1.789l-.894-1.789zm2.894.789a1 1 0 1 0-.894-1.789l.894 1.789zM8 19a1 1 0 1 0 2 0H8zm2-2.5a1 1 0 1 0-2 0h2zm-7.447.394a1 1 0 1 0 .894-1.789l-.894 1.789zM1 15H0a1 1 0 0 0 .553.894L1 15zm1-2.5a1 1 0 1 0-2 0h2zm12.553 2.606a1 1 0 1 0 .894 1.789l-.894-1.789zM17 15l.447.894A1 1 0 0 0 18 15h-1zm1-2.5a1 1 0 1 0-2 0h2zm-7.447-5.394l-2 1 .894 1.789 2-1-.894-1.789zm-1.106 1l-2-1-.894 1.789 2 1 .894-1.789zM8 9v2.5h2V9H8zm8.553-4.894l-2 1 .894 1.789 2-1-.894-1.789zm.894 0l-2-1-.894 1.789 2 1 .894-1.789zM16 5v2.5h2V5h-2zm-4.553-3.894l-2-1-.894 1.789 2 1 .894-1.789zm-2.894-1l-2 1 .894 1.789 2-1L8.553.106zM1.447 5.894l2-1-.894-1.789-2 1 .894 1.789zm-.894 0l2 1 .894-1.789-2-1-.894 1.789zM0 5v2.5h2V5H0zm9.447 13.106l-2-1-.894 1.789 2 1 .894-1.789zm0 1.789l2-1-.894-1.789-2 1 .894 1.789zM10 19v-2.5H8V19h2zm-6.553-3.894l-2-1-.894 1.789 2 1 .894-1.789zM2 15v-2.5H0V15h2zm13.447 1.894l2-1-.894-1.789-2 1 .894 1.789zM18 15v-2.5h-2V15h2z" />
</svg>
</template>

View File

@ -1,6 +0,0 @@
<template>
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor">
<path
d="M10 3.22l-.61-.6a5.5 5.5 0 0 0-7.666.105 5.5 5.5 0 0 0-.114 7.665L10 18.78l8.39-8.4a5.5 5.5 0 0 0-.114-7.665 5.5 5.5 0 0 0-7.666-.105l-.61.61z" />
</svg>
</template>

View File

@ -1,17 +0,0 @@
<!-- This icon is from <https://github.com/Templarian/MaterialDesign>, distributed under Apache 2.0 (https://www.apache.org/licenses/LICENSE-2.0) license-->
<template>
<svg
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
aria-hidden="true"
role="img"
class="iconify iconify--mdi"
width="24"
height="24"
preserveAspectRatio="xMidYMid meet"
viewBox="0 0 24 24">
<path
d="M20 18v-4h-3v1h-2v-1H9v1H7v-1H4v4h16M6.33 8l-1.74 4H7v-1h2v1h6v-1h2v1h2.41l-1.74-4H6.33M9 5v1h6V5H9m12.84 7.61c.1.22.16.48.16.8V18c0 .53-.21 1-.6 1.41c-.4.4-.85.59-1.4.59H4c-.55 0-1-.19-1.4-.59C2.21 19 2 18.53 2 18v-4.59c0-.32.06-.58.16-.8L4.5 7.22C4.84 6.41 5.45 6 6.33 6H7V5c0-.55.18-1 .57-1.41C7.96 3.2 8.44 3 9 3h6c.56 0 1.04.2 1.43.59c.39.41.57.86.57 1.41v1h.67c.88 0 1.49.41 1.83 1.22l2.34 5.39z"
fill="currentColor"></path>
</svg>
</template>

View File

@ -1,13 +1,13 @@
<!DOCTYPE html>
<html lang="">
<head>
<meta charset="UTF-8">
<link rel="icon" href="/favicon.ico">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Vite App</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/main.ts"></script>
</body>
<head>
<meta charset="UTF-8">
<link href="./assets/index.svg" rel="icon">
<meta content="width=device-width, initial-scale=0.8" name="viewport">
<title>pan-disk</title>
</head>
<body>
<main id="app"></main>
</body>
<script src="/main.ts" type="module"></script>
</html>

View File

@ -0,0 +1,30 @@
{
"$schema": "../../languageSchema.json",
"iconsUrl": "https://icones.js.org/collection/emojione-v1?category=Flags",
"languages": [
{
"title": "zh-CN 简体中文",
"id": "zh-CN",
"file": "zh-CN.json",
"icon": "emojione-v1:flag-for-china"
},
{
"title": "zh-TW 简体中文",
"id": "zh-TW",
"file": "zh-TW.json",
"icon": "emojione-v1:flag-for-china"
},
{
"title": "en-US English",
"id": "en-US",
"file": "en-US.json",
"icon": "emojione-v1:flag-for-united-states"
},
{
"title": "ja-JP 日本語",
"id": "ja-JP",
"file": "ja-JP.json",
"icon": "emojione-v1:flag-for-japan"
}
]
}

View File

@ -0,0 +1,35 @@
{
"message": {
"login": {
"plaselogin": "Plase login."
}
},
"nav": {
"title": {
"api": "API",
"file": "file",
"home": "Home",
"setting": "setting",
"user": "user"
}
},
"setting": {
"language": {
"title": "language"
},
"theme": {
"auto": "auto",
"dark": "dark",
"light": "light",
"title": "theme"
},
"title": "setting"
},
"view": {
"login": {
"password": "password",
"title": "login",
"username": "username"
}
}
}

View File

@ -0,0 +1,35 @@
{
"message": {
"login": {
"plaselogin": "ログインしてください"
}
},
"nav": {
"title": {
"api": "API",
"file": "ファイル",
"home": "ホーム",
"setting": "設定",
"user": "ユーザー"
}
},
"setting": {
"language": {
"title": "言語設定"
},
"theme": {
"auto": "自動",
"dark": "ダークモード",
"light": "ライトモード",
"title": "テーマ設定"
},
"title": "設定"
},
"view": {
"login": {
"password": "パスワード",
"title": "ログイン",
"username": "ユーザー名"
}
}
}

View File

@ -0,0 +1,35 @@
{
"message": {
"login": {
"plaselogin": "请登陆"
}
},
"nav": {
"title": {
"api": "API",
"file": "文件",
"home": "主页",
"setting": "设置",
"user": "用户"
}
},
"setting": {
"language": {
"title": "语言配置"
},
"theme": {
"auto": "自动",
"dark": "暗色",
"light": "亮色",
"title": "主题配置"
},
"title": "设置"
},
"view": {
"login": {
"password": "密码",
"title": "登陆",
"username": "用户名"
}
}
}

View File

@ -0,0 +1,35 @@
{
"message": {
"login": {
"plaselogin": "請登入"
}
},
"nav": {
"title": {
"api": "API",
"file": "檔案",
"home": "首頁",
"setting": "設定",
"user": "使用者"
}
},
"setting": {
"language": {
"title": "語言設定"
},
"theme": {
"auto": "自動",
"dark": "深色",
"light": "淺色",
"title": "主題設定"
},
"title": "設定"
},
"view": {
"login": {
"password": "密碼",
"title": "登入",
"username": "使用者名稱"
}
}
}

259
web-src/layout/Head.vue Normal file
View File

@ -0,0 +1,259 @@
<template>
<nav class="navmain">
<router-link :to="{ name: 'home' }" c-c f style="color: inherit; height: 100%; text-decoration: none">
<img alt="@/assets/index.svg" height="50" src="@/assets/index.svg" width="50" />
<div>
<label style="font-size: 20px; margin-left: 10px; cursor: pointer">{{ useSettingStore.appName }}</label>
<label style="margin-left: 5px; font-size: 12px; cursor: pointer">V{{ useSettingStore.appVersion }}</label>
</div>
</router-link>
<div style="flex-grow: 1"></div>
<div c-c f style="height: 100%">
<div
v-bind:class="{
'router-item-atc': router.currentRoute.value.name === 'home',
'router-item': true,
}"
@click="router.push({ name: 'home' })">
<Icon icon="material-symbols:home" />
{{ t('nav.title.home') }}
<div class="after"></div>
</div>
<div
v-bind:class="{
'router-item-atc': router.currentRoute.value.path.startsWith('/file'),
'router-item': true,
}"
@click="openFile()">
<Icon icon="material-symbols:folder-rounded" />
{{ t('nav.title.file') }}
<div class="after"></div>
</div>
<div
v-bind:class="{
'router-item-atc': router.currentRoute.value.name === 'user',
'router-item': true,
}"
@click="router.push({ name: 'user' })">
<Icon icon="material-symbols:person-rounded" />
{{ t('nav.title.user') }}
<div class="after"></div>
</div>
<div
v-bind:class="{
'router-item-atc': router.currentRoute.value.name === 'api',
'router-item': true,
}"
@click="router.push({ name: 'api' })">
<icon icon="mdi:api" />
{{ t('nav.title.api') }}
<div class="after"></div>
</div>
<div
v-bind:class="{
'router-item': true,
}"
@click="activeSetting.on()">
<Icon icon="material-symbols:settings" />
{{ t('nav.title.setting') }}
<div class="after"></div>
</div>
</div>
</nav>
<nav class="nav">
<n-drawer v-model:show="activeSetting.value" placement="right" width="auto">
<n-drawer-content :title="t('setting.title')">
<n-divider>{{ t('setting.theme.title') }}</n-divider>
<div c-c f>
<n-radio-group v-model:value="useSettingStore.themeMode" name="team">
<n-radio-button value="light">{{ t('setting.theme.light') }}</n-radio-button>
<n-radio-button value="dark">{{ t('setting.theme.dark') }}</n-radio-button>
<n-radio-button value="auto">{{ t('setting.theme.auto') }}</n-radio-button>
</n-radio-group>
</div>
<n-divider>{{ t('setting.language.title') }}</n-divider>
<n-select
:options="messageData"
:render-label="renderLabel"
:render-tag="renderMultipleSelectTag"
:value="useSettingStore.language"
filterable
@update-value="useSettingStore.setLanguage"></n-select>
</n-drawer-content>
</n-drawer>
</nav>
</template>
<script lang="ts" setup>
import Icon from '@/components/Icon.vue';
import { UseBoolRef } from '@/util';
import {
type languageIndexItemValueType,
message,
messageData,
router,
t,
UseAuthStore,
UseSettingStore,
} from '@/plugin';
import { getSysInfo } from '@/api/system.ts';
import type { SelectOption } from 'naive-ui/es/select/src/interface';
import type { VNodeChild } from 'vue';
export type RenderTag = (
props: {
option: SelectOption;
handleClose: () => void;
} & languageIndexItemValueType,
) => VNodeChild;
export type RenderTagSelect = (props: {
option: SelectOption & languageIndexItemValueType;
handleClose: () => void;
}) => VNodeChild;
const renderLabel: RenderTag = (data) => {
return h(
'div',
{
f: '',
'n-c': '',
style: {
gap: '10px',
},
},
[h(Icon, { icon: data.icon }), data.title],
);
};
const renderMultipleSelectTag: RenderTagSelect = ({ option: data }) => {
return h(
'div',
{
f: '',
'n-c': '',
style: {
gap: '10px',
},
},
[h(Icon, { icon: data.icon }), data.title],
);
};
const useSettingStore = UseSettingStore();
const activeSetting = UseBoolRef();
const useAuthStore = UseAuthStore();
getSysInfo().then((r) => {
const data = r.json().data;
useSettingStore.appName = data.appName;
useSettingStore.appVersion = data.appVersion;
document.title = data.appName;
});
useAuthStore.woIsMe();
useSettingStore.setLanguage(null);
function openFile() {
if (useAuthStore.isLogin) {
router.push({
path: `/file/${useAuthStore.username}`,
});
} else {
message.error(() => t('message.login.plaselogin'));
router.push({
path: `/login`,
});
}
}
console.log(t);
</script>
<style lang="scss" scoped>
html.dark {
.router-item-atc {
--atc-color: #66ffad;
&:hover {
--atc-color: #66b3ff !important;
}
}
.router-item {
&:hover {
--atc-color: #965522;
background-color: rgba(255, 255, 255, 0.15);
}
}
}
.router-item-atc {
--atc-color: #64a8fc;
&:hover {
--atc-color: #42e3e8;
}
}
:where(.router-item) {
cursor: pointer;
min-width: 50px;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
height: 100%;
position: relative;
--atc-color: rgba(255, 255, 255, 0);
&:hover {
--atc-color: #ffd56f;
background-color: rgba(0, 0, 0, 0.15);
}
.after {
width: 100%;
height: 2px;
position: absolute;
bottom: 0;
background-color: var(--atc-color);
}
}
.nvr-button {
border-radius: 10px;
cursor: pointer;
transition: background-color 0.3s ease;
}
.nvr-button:hover {
background-color: #d8d8d8;
}
html.dark {
.nvr-button:hover {
background-color: #404040;
}
}
.nav {
height: 66px;
width: 100%;
}
.navmain {
position: fixed;
backdrop-filter: blur(5px);
height: 65px;
width: calc(100% - 40px);
z-index: 5;
padding: 0 20px;
display: flex;
background-color: rgba(221, 221, 221, 0.6);
border-bottom: #d3d3d3 1px solid;
}
html.dark {
.navmain {
background-color: rgba(0, 0, 0, 0.6);
border-bottom: #424242 1px solid;
}
}
</style>

12
web-src/layout/Router.vue Normal file
View File

@ -0,0 +1,12 @@
<template>
<Head />
<n-back-top />
<router-view v-slot="{ Component }">
<div style="margin: 10px; height: calc(100% - 66px - 20px)">
<component :is="Component" />
</div>
</router-view>
</template>
<script lang="ts" setup>
import Head from '@/layout/Head.vue';
</script>

11
web-src/layout/index.vue Normal file
View File

@ -0,0 +1,11 @@
<template>
<RouterView />
</template>
<script lang="ts" setup>
import { useLoadingBar, useMessage } from 'naive-ui';
import { setLoadingBar, setMessage } from '@/plugin';
setMessage(useMessage());
setLoadingBar(useLoadingBar());
</script>
<style scoped></style>

View File

@ -1,14 +1,15 @@
import './assets/main.css';
import './assets/index.scss';
import './assets/index.sass';
import { createApp } from 'vue';
import { createPinia } from 'pinia';
import App from './App.vue';
import router from './router';
import { i18n, installI18n, pinia, router } from './plugin';
const app = createApp(App);
app.use(createPinia());
app.use(router);
app.mount('#app');
app.use(pinia);
app.use(i18n);
installI18n().then(() => {
app.use(router);
app.mount('#app');
});

View File

@ -0,0 +1,47 @@
import { createAlova } from 'alova';
import adapterFetch from 'alova/fetch';
import { message, UseAuthStore } from '@/plugin';
export * from './type';
export const alovaInstance = createAlova({
requestAdapter: adapterFetch(),
cacheFor: { expire: 0 },
responded: async (response) => {
const text = await response.text();
const data = {
response: () => response,
text: () => text,
json: () => JSON.parse(text),
headers: () => {
const headers: { [key: string]: string } = {};
response.headers.forEach((k, v) => {
headers[k] = v;
});
return headers;
},
};
if (response.status !== 200) {
const messages = JSON.parse(text).message;
if (response.status === 500) message.error(messages);
throw new RequestError(data, messages);
}
return data;
},
beforeRequest(method) {
const useAuthStore = UseAuthStore();
if (useAuthStore.isLogin) {
method.config.headers.Authorization = `Bearer ${useAuthStore.token}`;
}
},
});
export class RequestError extends Error {
public data: any;
constructor(data: any, message: string) {
super(message);
this.data = data;
this.name = 'RequestError';
}
}

View File

@ -0,0 +1,13 @@
export interface AlovaResponseType<K = any> {
json: () => ResponseServer<K>;
text: () => string;
response: () => Response;
headers: () => { [key: string]: string };
}
export interface ResponseServer<K> {
code: number;
message: string;
data: K;
dateTime: string;
}

View File

@ -0,0 +1,58 @@
import { createI18n } from 'vue-i18n';
import type { languageIndexItemValueType, languageIndexType, languageType } from './type';
import { ref } from 'vue';
export * from './type';
const languagedatas = import.meta.glob('@/language/**/*.json');
const loadIndexFile = async (): Promise<languageIndexType> => {
try {
const module = (await languagedatas['/language/index.json']()) as {
default: languageIndexType;
};
return module.default;
} catch (error) {
console.error('加载语言索引文件失败:', error);
throw error;
}
};
const loadLanguageFile = async (fileName: string): Promise<languageType> => {
try {
const module = (await languagedatas[`/language/${fileName}`]()) as {
default: languageType;
};
return module.default;
} catch (error) {
console.error(`加载语言文件失败:${fileName}`, error);
return {};
}
};
export const messages: languageType | any = {};
export const messageData = ref<languageIndexItemValueType[]>([]);
export const i18n = createI18n({
legacy: false,
locale: 'zh-cn',
fallbackLocale: 'zh-cn',
messages,
});
export async function installI18n() {
const data: languageIndexType = await loadIndexFile();
for (const i of data.languages) {
messageData.value.push({
title: i.title,
value: i.id,
icon: i.icon,
});
messages[i.id] = await loadLanguageFile(`lang/${i.file}`);
}
}
export function t(d: string): string {
return i18n.global.t(d);
}

View File

@ -0,0 +1,18 @@
export type languageType = { [key: string]: string | languageType };
export interface languageIndexItemType {
title: string;
id: string;
file: string;
icon: string;
}
export interface languageIndexItemValueType {
title: string;
value: string;
icon: string;
}
export interface languageIndexType {
languages: languageIndexItemType[];
}

18
web-src/plugin/index.ts Normal file
View File

@ -0,0 +1,18 @@
import type { MessageApiInjection } from 'naive-ui/es/message/src/MessageProvider';
import type { LoadingBarApiInjection } from 'naive-ui/es/loading-bar/src/LoadingBarProvider';
export * from './router';
export * from './stores';
export * from './alova';
export * from './i18n';
export let message: MessageApiInjection;
export let loadingBar: LoadingBarApiInjection;
export function setMessage(messages: MessageApiInjection) {
message = messages;
}
export function setLoadingBar(loadingBars: LoadingBarApiInjection) {
loadingBar = loadingBars;
}

View File

@ -0,0 +1,52 @@
import { createRouter, createWebHistory } from 'vue-router';
import { loadingBar } from '@/plugin';
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes: [
{
path: '/',
component: () => import('@/layout/Router.vue'),
children: [
{
path: '/',
name: 'home',
component: () => import('@/views/HomeView.vue'),
},
{
path: '/file/:pathMatch(.*)*',
name: 'file',
component: () => import('@/views/FileView.vue'),
},
{
path: '/user',
name: 'user',
component: () => import('@/views/UserView.vue'),
},
{
path: '/login',
name: 'login',
component: () => import('@/views/loginView.vue'),
},
{
path: '/register',
name: 'register',
component: () => import('@/views/RegisterView.vue'),
},
{
path: '/swagger',
name: 'api',
component: () => import('@/views/SwaggerView.vue'),
},
],
},
],
});
router.beforeEach((to, from, next) => {
loadingBar.start();
next();
setTimeout(() => loadingBar.finish(), 100);
});
export { router };

View File

@ -0,0 +1,49 @@
import { defineStore } from 'pinia';
import { login, logout, woIsMe } from '@/api';
import CookiesApi from '@/util/Cookies.ts';
export const UseAuthStore = defineStore('auth', {
state: () => ({
username: '',
password: '',
isAdmin: false,
isLogin: false,
userId: '',
prermissions: Array<string>(),
roles: Array<string>(),
token: '',
isRemberMe: false,
}),
actions: {
login(username: string, password: string) {
login({ username, password }).then((r) => {
if (this.isRemberMe) {
this.username = username;
this.password = password;
}
this.token = r.json().data;
this.isLogin = true;
this.woIsMe();
});
},
woIsMe() {
if (this.isLogin)
woIsMe()
.then((r) => {
const data = r.json().data;
this.username = data.username;
this.isAdmin = data.admin;
this.prermissions = data.prermissions;
this.roles = data.roles;
this.userId = data.id;
})
.catch(() => this.$reset());
},
logout() {
logout().then(() => this.$reset());
},
},
persist: {
storage: CookiesApi.StorageApi,
},
});

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