Compare commits
20 Commits
Auto-Relea
...
master
Author | SHA1 | Date | |
---|---|---|---|
067889f096 | |||
953a370233 | |||
7f8defeee2 | |||
67d99ccd42 | |||
2a6672613a | |||
3071859a3d | |||
570c41bc11 | |||
dac6928844 | |||
c51326b698 | |||
ddaa02da3d | |||
704da3a692 | |||
10580a0d63 | |||
2e2898603f | |||
9de51dd7ee | |||
9d476c338b | |||
bd846f4273 | |||
384fbf3cde | |||
9c9493eb56 | |||
3a83354011 | |||
147e5551a7 |
@ -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
|
||||
|
@ -12,10 +12,10 @@ jobs:
|
||||
- name: Check out repository code
|
||||
uses: https://git.mingliqiye.com/Actions/checkout@v4
|
||||
|
||||
- name: build-test
|
||||
- name: Build
|
||||
run: |
|
||||
pnpm install
|
||||
pnpm run build-jar
|
||||
pnpm run build-jar-auto
|
||||
|
||||
- name: Releases
|
||||
run: |
|
||||
|
8
.gitignore
vendored
8
.gitignore
vendored
@ -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
17
application.yaml
Normal 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
|
@ -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
55
languageSchema.json
Normal 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
|
||||
}
|
24
package.json
24
package.json
@ -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"
|
||||
}
|
||||
|
@ -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) {
|
||||
|
64
src/main/java/com/mingliqiye/disk/cache/MyBatisRedisCache.java
vendored
Normal file
64
src/main/java/com/mingliqiye/disk/cache/MyBatisRedisCache.java
vendored
Normal 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()));
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
23
src/main/java/com/mingliqiye/disk/config/RedisConfig.java
Normal file
23
src/main/java/com/mingliqiye/disk/config/RedisConfig.java
Normal 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;
|
||||
}
|
||||
}
|
@ -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("/**");
|
||||
}
|
||||
}
|
@ -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/");
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
14
src/main/java/com/mingliqiye/disk/configuration/Config.java
Normal file
14
src/main/java/com/mingliqiye/disk/configuration/Config.java
Normal 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;
|
||||
}
|
@ -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;
|
||||
}
|
15
src/main/java/com/mingliqiye/disk/configuration/Mysql.java
Normal file
15
src/main/java/com/mingliqiye/disk/configuration/Mysql.java
Normal 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;
|
||||
}
|
15
src/main/java/com/mingliqiye/disk/configuration/Redis.java
Normal file
15
src/main/java/com/mingliqiye/disk/configuration/Redis.java
Normal 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;
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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()));
|
||||
}
|
||||
}
|
14
src/main/java/com/mingliqiye/disk/dto/auth/Login.java
Normal file
14
src/main/java/com/mingliqiye/disk/dto/auth/Login.java
Normal 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;
|
||||
}
|
12
src/main/java/com/mingliqiye/disk/dto/system/Info.java
Normal file
12
src/main/java/com/mingliqiye/disk/dto/system/Info.java
Normal 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;
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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()));
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -0,0 +1,6 @@
|
||||
package com.mingliqiye.disk.function;
|
||||
|
||||
@FunctionalInterface
|
||||
public interface P1Function<P> {
|
||||
void call(P p);
|
||||
}
|
@ -0,0 +1,6 @@
|
||||
package com.mingliqiye.disk.function;
|
||||
|
||||
@FunctionalInterface
|
||||
public interface P1R1Function<P, R> {
|
||||
R call(P p);
|
||||
}
|
82
src/main/java/com/mingliqiye/disk/http/Respose.java
Normal file
82
src/main/java/com/mingliqiye/disk/http/Respose.java
Normal 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);
|
||||
}
|
||||
}
|
@ -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> {}
|
53
src/main/java/com/mingliqiye/disk/model/User.java
Normal file
53
src/main/java/com/mingliqiye/disk/model/User.java
Normal 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;
|
||||
}
|
||||
}
|
241
src/main/java/com/mingliqiye/disk/time/DateTime.java
Normal file
241
src/main/java/com/mingliqiye/disk/time/DateTime.java
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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));
|
||||
}
|
||||
}
|
264
src/main/java/com/mingliqiye/disk/util/FileUtil.java
Normal file
264
src/main/java/com/mingliqiye/disk/util/FileUtil.java
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
3
src/main/java/com/mingliqiye/disk/util/HashUtil.java
Normal file
3
src/main/java/com/mingliqiye/disk/util/HashUtil.java
Normal file
@ -0,0 +1,3 @@
|
||||
package com.mingliqiye.disk.util;
|
||||
|
||||
public class HashUtil {}
|
17
src/main/java/com/mingliqiye/disk/util/ListUtil.java
Normal file
17
src/main/java/com/mingliqiye/disk/util/ListUtil.java
Normal 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> {}
|
||||
}
|
78
src/main/java/com/mingliqiye/disk/util/LocalHostUtil.java
Normal file
78
src/main/java/com/mingliqiye/disk/util/LocalHostUtil.java
Normal 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;
|
||||
}
|
||||
}
|
17
src/main/java/com/mingliqiye/disk/util/NullUtil.java
Normal file
17
src/main/java/com/mingliqiye/disk/util/NullUtil.java
Normal 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("***");
|
||||
}
|
||||
}
|
||||
}
|
23
src/main/java/com/mingliqiye/disk/util/PanStpUtil.java
Normal file
23
src/main/java/com/mingliqiye/disk/util/PanStpUtil.java
Normal 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();
|
||||
}
|
||||
}
|
66
src/main/java/com/mingliqiye/disk/util/StringUtil.java
Normal file
66
src/main/java/com/mingliqiye/disk/util/StringUtil.java
Normal 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();
|
||||
}
|
||||
}
|
40
src/main/java/com/mingliqiye/disk/util/SystemUtil.java
Normal file
40
src/main/java/com/mingliqiye/disk/util/SystemUtil.java
Normal 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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
141
src/main/java/com/mingliqiye/disk/uuid/UUID.java
Normal file
141
src/main/java/com/mingliqiye/disk/uuid/UUID.java
Normal 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();
|
||||
}
|
||||
}
|
12
src/main/java/com/mingliqiye/disk/uuid/UUIDException.java
Normal file
12
src/main/java/com/mingliqiye/disk/uuid/UUIDException.java
Normal 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);
|
||||
}
|
||||
}
|
@ -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());
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
90
src/main/resources/application.yaml
Normal file
90
src/main/resources/application.yaml
Normal 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
|
6
src/main/resources/banner/banner.txt
Normal file
6
src/main/resources/banner/banner.txt
Normal file
@ -0,0 +1,6 @@
|
||||
__ __ _____ _ _ _____ _ _____ ____ _____ __ __ ______
|
||||
| \/ | |_ _| | \ | | / ____| | | |_ _| / __ \ |_ _| \ \ / / | ____|
|
||||
| \ / | | | | \| | | | __ | | | | | | | | | | \ \_/ / | |__
|
||||
| |\/| | | | | . ` | | | |_ | | | | | | | | | | | \ / | __|
|
||||
| | | | _| |_ | |\ | | |__| | | |____ _| |_ | |__| | _| |_ | | | |____
|
||||
|_| |_| |_____| |_| \_| \_____| |______| |_____| \___\_\ |_____| |_| |______|
|
29
src/main/resources/log4j2.yaml
Normal file
29
src/main/resources/log4j2.yaml
Normal 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
|
3
src/main/resources/sql/database.sql
Normal file
3
src/main/resources/sql/database.sql
Normal 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'@'%'
|
28
src/main/resources/sql/users.sql
Normal file
28
src/main/resources/sql/users.sql
Normal 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);
|
@ -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()));
|
||||
}
|
||||
}
|
||||
|
@ -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/*"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
100
web-src/App.vue
100
web-src/App.vue
@ -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
33
web-src/api/auth.ts
Normal 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
1
web-src/api/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from './auth';
|
10
web-src/api/system.ts
Normal file
10
web-src/api/system.ts
Normal 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;
|
||||
}
|
1257
web-src/assets/SwaggerDark.scss
Normal file
1257
web-src/assets/SwaggerDark.scss
Normal file
File diff suppressed because it is too large
Load Diff
@ -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
17
web-src/assets/index.sass
Normal 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
143
web-src/assets/index.scss
Normal 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
9
web-src/assets/index.svg
Normal 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 |
@ -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 |
@ -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
18
web-src/assets/var.scss
Normal 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;
|
||||
}
|
||||
|
||||
|
@ -1,43 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
defineProps<{
|
||||
msg: string;
|
||||
}>();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="greetings">
|
||||
<h1 class="green">{{ msg }}</h1>
|
||||
<h3>
|
||||
You’ve 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>
|
41
web-src/components/Icon.vue
Normal file
41
web-src/components/Icon.vue
Normal 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>
|
21
web-src/components/MCode.vue
Normal file
21
web-src/components/MCode.vue
Normal 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>
|
@ -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>
|
||||
|
||||
Vue’s
|
||||
<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>
|
@ -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>
|
@ -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>
|
@ -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>
|
@ -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>
|
@ -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>
|
@ -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>
|
@ -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>
|
||||
|
30
web-src/language/index.json
Normal file
30
web-src/language/index.json
Normal 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"
|
||||
}
|
||||
]
|
||||
}
|
35
web-src/language/lang/en-US.json
Normal file
35
web-src/language/lang/en-US.json
Normal 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"
|
||||
}
|
||||
}
|
||||
}
|
35
web-src/language/lang/ja-JP.json
Normal file
35
web-src/language/lang/ja-JP.json
Normal 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": "ユーザー名"
|
||||
}
|
||||
}
|
||||
}
|
35
web-src/language/lang/zh-CN.json
Normal file
35
web-src/language/lang/zh-CN.json
Normal 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": "用户名"
|
||||
}
|
||||
}
|
||||
}
|
35
web-src/language/lang/zh-TW.json
Normal file
35
web-src/language/lang/zh-TW.json
Normal 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
259
web-src/layout/Head.vue
Normal 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
12
web-src/layout/Router.vue
Normal 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
11
web-src/layout/index.vue
Normal 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>
|
@ -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');
|
||||
});
|
||||
|
47
web-src/plugin/alova/index.ts
Normal file
47
web-src/plugin/alova/index.ts
Normal 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';
|
||||
}
|
||||
}
|
13
web-src/plugin/alova/type.ts
Normal file
13
web-src/plugin/alova/type.ts
Normal 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;
|
||||
}
|
58
web-src/plugin/i18n/index.ts
Normal file
58
web-src/plugin/i18n/index.ts
Normal 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);
|
||||
}
|
18
web-src/plugin/i18n/type.ts
Normal file
18
web-src/plugin/i18n/type.ts
Normal 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
18
web-src/plugin/index.ts
Normal 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;
|
||||
}
|
52
web-src/plugin/router/index.ts
Normal file
52
web-src/plugin/router/index.ts
Normal 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 };
|
49
web-src/plugin/stores/auth.ts
Normal file
49
web-src/plugin/stores/auth.ts
Normal 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
Loading…
x
Reference in New Issue
Block a user