From 8955b0196f631c04299dad737a9e9af0025ee6d7 Mon Sep 17 00:00:00 2001
From: msslulu <1484036491@qq.com>
Date: Fri, 14 Aug 2026 04:43:37 -0700
Subject: [PATCH 1/4] Application
---
template/nestJs/libs/redis/redis.service.ts | 1 +
template/springboot/pom.xml | 10 +-
.../controller/ApplicationController.java | 45 ++++
.../entity/dto/CreateApplicationDto.java | 25 +++
.../entity/dto/PaginationQueryDto.java | 29 ++-
.../com/TinyPro/entity/po/Application.java | 57 +++++
.../com/TinyPro/entity/vo/ApplicationVo.java | 32 +++
.../TinyPro/jpa/ApplicationRepository.java | 12 +
.../TinyPro/service/ApplicationService.java | 21 ++
.../service/imp/ApplicationServiceImpl.java | 106 +++++++++
.../src/main/resources/application.properties | 7 +-
.../resources/i18n/messages_zh_CN.properties | 209 +++++++++---------
12 files changed, 437 insertions(+), 117 deletions(-)
create mode 100644 template/springboot/src/main/java/com/TinyPro/controller/ApplicationController.java
create mode 100644 template/springboot/src/main/java/com/TinyPro/entity/dto/CreateApplicationDto.java
create mode 100644 template/springboot/src/main/java/com/TinyPro/entity/po/Application.java
create mode 100644 template/springboot/src/main/java/com/TinyPro/entity/vo/ApplicationVo.java
create mode 100644 template/springboot/src/main/java/com/TinyPro/jpa/ApplicationRepository.java
create mode 100644 template/springboot/src/main/java/com/TinyPro/service/ApplicationService.java
create mode 100644 template/springboot/src/main/java/com/TinyPro/service/imp/ApplicationServiceImpl.java
diff --git a/template/nestJs/libs/redis/redis.service.ts b/template/nestJs/libs/redis/redis.service.ts
index 507a4c40..1e13683d 100644
--- a/template/nestJs/libs/redis/redis.service.ts
+++ b/template/nestJs/libs/redis/redis.service.ts
@@ -7,6 +7,7 @@ export class RedisService {
constructor() {
this.redisClient = new Redis({
host: process.env.REDIS_HOST,
+ password: process.env.REDIS_PASSWORD,
port: parseInt(process.env.REDIS_PORT),
});
}
diff --git a/template/springboot/pom.xml b/template/springboot/pom.xml
index 0eee262e..15e7ded0 100644
--- a/template/springboot/pom.xml
+++ b/template/springboot/pom.xml
@@ -49,10 +49,12 @@
org.springframework.boot
spring-boot-starter-validation
-
- org.springframework.boot
- spring-boot-starter-validation
-
+
+ org.springdoc
+ springdoc-openapi-starter-webmvc-ui
+ 2.5.0
+
+
mysql
diff --git a/template/springboot/src/main/java/com/TinyPro/controller/ApplicationController.java b/template/springboot/src/main/java/com/TinyPro/controller/ApplicationController.java
new file mode 100644
index 00000000..1d9f1aec
--- /dev/null
+++ b/template/springboot/src/main/java/com/TinyPro/controller/ApplicationController.java
@@ -0,0 +1,45 @@
+package com.TinyPro.controller;
+
+import com.TinyPro.entity.dto.CreateApplicationDto;
+import com.TinyPro.entity.dto.PaginationQueryDto;
+import com.TinyPro.entity.po.Application;
+import com.TinyPro.entity.vo.ApplicationVo;
+import com.TinyPro.service.ApplicationService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.media.Content;
+import io.swagger.v3.oas.annotations.media.Schema;
+import io.swagger.v3.oas.annotations.responses.ApiResponse;
+import io.swagger.v3.oas.annotations.responses.ApiResponses;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.validation.Valid;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+@RestController
+@RequestMapping("/application")
+@Tag(name = "应用管理", description = "应用相关接口")
+public class ApplicationController {
+ @Autowired
+ private ApplicationService applicationService;
+
+ @Operation(summary = "获取应用列表", description = "分页查询应用,支持关键词搜索和分类过滤")
+ @ApiResponses(value = {
+ @ApiResponse(responseCode = "200", description = "成功",
+ content = @Content(schema = @Schema(implementation = ApplicationVo.class)))
+ })
+ @GetMapping
+ public ApplicationVo getAllApplication(PaginationQueryDto searchInfo) {
+ return applicationService.findAllApplication(searchInfo);
+ }
+
+ @Operation(summary = "创建应用", description = "创建新应用,支持初始化模式(已存在则返回)")
+ @ApiResponses(value = {
+ @ApiResponse(responseCode = "200", description = "创建成功"),
+ @ApiResponse(responseCode = "400", description = "应用已存在")
+ })
+ @PostMapping
+ public Application createApplication(@Valid @RequestBody CreateApplicationDto dto,
+ @RequestParam(defaultValue = "false") boolean isInit) {
+ return applicationService.createApplication(dto, isInit);
+ }
+}
diff --git a/template/springboot/src/main/java/com/TinyPro/entity/dto/CreateApplicationDto.java b/template/springboot/src/main/java/com/TinyPro/entity/dto/CreateApplicationDto.java
new file mode 100644
index 00000000..246e37cc
--- /dev/null
+++ b/template/springboot/src/main/java/com/TinyPro/entity/dto/CreateApplicationDto.java
@@ -0,0 +1,25 @@
+package com.TinyPro.entity.dto;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotBlank;
+import lombok.Data;
+
+@Data
+@Schema(description = "创建应用请求")
+public class CreateApplicationDto {
+ @NotBlank
+ @Schema(description = "应用名称", required = true)
+ private String name;
+
+ @Schema(description = "描述")
+ private String description;
+
+ @Schema(description = "标签(JSON 字符串)")
+ private String tag;
+
+ @Schema(description = "图标")
+ private String icon;
+
+ @Schema(description = "分类")
+ private String classify;
+}
diff --git a/template/springboot/src/main/java/com/TinyPro/entity/dto/PaginationQueryDto.java b/template/springboot/src/main/java/com/TinyPro/entity/dto/PaginationQueryDto.java
index f3db5217..4b2f893c 100644
--- a/template/springboot/src/main/java/com/TinyPro/entity/dto/PaginationQueryDto.java
+++ b/template/springboot/src/main/java/com/TinyPro/entity/dto/PaginationQueryDto.java
@@ -1,18 +1,31 @@
package com.TinyPro.entity.dto;
+import jakarta.validation.constraints.Max;
import jakarta.validation.constraints.Min;
import lombok.Data;
-import org.springframework.boot.context.properties.ConfigurationProperties;
-import org.springframework.core.convert.converter.Converter;
-import org.springframework.stereotype.Component;
+import io.swagger.v3.oas.annotations.Parameter;
+
+
+import org.springdoc.core.annotations.ParameterObject;
+
@Data
+@ParameterObject // 让 Springdoc 展开参数到 Swagger 文档中
public class PaginationQueryDto {
- @Min(1)
- private Integer page = 1; // 默认值1,可通过配置覆盖
+ @Parameter(description = "页数,必须是一个大于零的正整数", example = "1")
+ @Min(1)
+ private Integer page = 1; // 默认值 1
+
+ @Parameter(description = "页大小,必须是一个正整数,最大 100", example = "10")
+ @Min(1)
+ @Max(100)
+ private Integer limit = 1; // 默认值 1(与 NestJS 原代码一致,也可根据需要改为 10)
+
+ @Parameter(description = "类型")
+ private String classify; // 可选,不加校验
- @Min(1)
- private Integer limit = 10; // 默认值10,可通过配置覆盖
-}
\ No newline at end of file
+ @Parameter(description = "关键字")
+ private String keywords;
+}
diff --git a/template/springboot/src/main/java/com/TinyPro/entity/po/Application.java b/template/springboot/src/main/java/com/TinyPro/entity/po/Application.java
new file mode 100644
index 00000000..bd35f277
--- /dev/null
+++ b/template/springboot/src/main/java/com/TinyPro/entity/po/Application.java
@@ -0,0 +1,57 @@
+package com.TinyPro.entity.po;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import jakarta.persistence.*;
+import lombok.Data;
+import org.hibernate.annotations.CreationTimestamp;
+import org.hibernate.annotations.UpdateTimestamp;
+import org.springframework.data.annotation.Id;
+
+import java.time.LocalDateTime;
+
+@Entity
+@Table(name = "application")
+@Data
+public class Application {
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ private Long id;
+
+ @Column(nullable = false, unique = true)
+ private String name;
+
+ private String description;
+
+ @Column(columnDefinition = "TEXT")
+ private String tag;
+
+ private String icon;
+
+ private String classify;
+
+ @CreationTimestamp
+ private LocalDateTime createdAt;
+
+ @UpdateTimestamp
+ private LocalDateTime updatedAt;
+
+ public Application(String name, String description, String tag, String icon, String classify) {
+ this.name = name;
+ this.description = description;
+ this.tag = tag;
+ this.icon = icon;
+ this.classify = classify;
+ }
+
+
+
+ // 安全解析 tag(非持久化方法)
+ public Object parseTagSafely() {
+ if (tag == null || tag.isBlank()) return new Object[0];
+ try {
+ return new ObjectMapper().readValue(tag, Object.class);
+ } catch (Exception e) {
+ return new Object[0];
+ }
+ }
+}
diff --git a/template/springboot/src/main/java/com/TinyPro/entity/vo/ApplicationVo.java b/template/springboot/src/main/java/com/TinyPro/entity/vo/ApplicationVo.java
new file mode 100644
index 00000000..c9ec8080
--- /dev/null
+++ b/template/springboot/src/main/java/com/TinyPro/entity/vo/ApplicationVo.java
@@ -0,0 +1,32 @@
+package com.TinyPro.entity.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.List;
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+@Schema(description = "应用分页响应")
+public class ApplicationVo {
+ @Schema(description = "应用列表")
+ private List data;
+
+ @Schema(description = "总记录数")
+ private long total;
+
+ @Data
+ @NoArgsConstructor
+ @AllArgsConstructor
+ @Schema(description = "应用条目")
+ public static class ApplicationItem {
+ private Long id;
+ private String name;
+ private String description;
+ private Object tag;
+ private String icon;
+ private String classify;
+ }
+}
diff --git a/template/springboot/src/main/java/com/TinyPro/jpa/ApplicationRepository.java b/template/springboot/src/main/java/com/TinyPro/jpa/ApplicationRepository.java
new file mode 100644
index 00000000..e9c9c118
--- /dev/null
+++ b/template/springboot/src/main/java/com/TinyPro/jpa/ApplicationRepository.java
@@ -0,0 +1,12 @@
+package com.TinyPro.jpa;
+
+import com.TinyPro.entity.po.Application;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
+import org.springframework.stereotype.Repository;
+
+@Repository
+public interface ApplicationRepository extends JpaRepository, JpaSpecificationExecutor {
+ Application findByName(String name);
+ boolean existsByName(String name);
+}
diff --git a/template/springboot/src/main/java/com/TinyPro/service/ApplicationService.java b/template/springboot/src/main/java/com/TinyPro/service/ApplicationService.java
new file mode 100644
index 00000000..af9c4ca4
--- /dev/null
+++ b/template/springboot/src/main/java/com/TinyPro/service/ApplicationService.java
@@ -0,0 +1,21 @@
+package com.TinyPro.service;
+
+import com.TinyPro.entity.dto.CreateApplicationDto;
+import com.TinyPro.entity.dto.PaginationQueryDto;
+import com.TinyPro.entity.po.Application;
+import com.TinyPro.entity.vo.ApplicationVo;
+
+public interface ApplicationService {
+ /**
+ * 分页查询应用列表
+ */
+ ApplicationVo findAllApplication(PaginationQueryDto searchInfo);
+
+ /**
+ * 创建应用
+ * @param dto 应用信息
+ * @param isInit 是否初始化模式(true: 已存在则直接返回,不抛异常;false: 已存在则抛异常)
+ * @return 已存在或新建的应用实体
+ */
+ Application createApplication(CreateApplicationDto dto, boolean isInit);
+}
diff --git a/template/springboot/src/main/java/com/TinyPro/service/imp/ApplicationServiceImpl.java b/template/springboot/src/main/java/com/TinyPro/service/imp/ApplicationServiceImpl.java
new file mode 100644
index 00000000..c9eca884
--- /dev/null
+++ b/template/springboot/src/main/java/com/TinyPro/service/imp/ApplicationServiceImpl.java
@@ -0,0 +1,106 @@
+package com.TinyPro.service.imp;
+
+import com.TinyPro.entity.dto.CreateApplicationDto;
+import com.TinyPro.entity.dto.PaginationQueryDto;
+import com.TinyPro.entity.po.Application;
+import com.TinyPro.entity.vo.ApplicationVo;
+import com.TinyPro.jpa.ApplicationRepository;
+import jakarta.persistence.criteria.Predicate;
+import jakarta.transaction.Transactional;
+import jakarta.validation.Valid;
+import lombok.RequiredArgsConstructor;
+import org.springframework.context.MessageSource;
+import org.springframework.context.i18n.LocaleContextHolder;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.PageRequest;
+import org.springframework.data.domain.Pageable;
+import org.springframework.data.jpa.domain.Specification;
+import org.springframework.http.HttpStatus;
+import org.springframework.stereotype.Service;
+import org.springframework.web.server.ResponseStatusException;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Locale;
+
+@Service
+@RequiredArgsConstructor
+public class ApplicationServiceImpl {
+
+ private final ApplicationRepository applicationRepository;
+ private final MessageSource messageSource;
+
+ // 分页查询
+ public ApplicationVo findAllApplication(PaginationQueryDto searchInfo) {
+ int page = searchInfo.getPage();
+ int limit = searchInfo.getLimit();
+ String keywords = searchInfo.getKeywords();
+ String classify = searchInfo.getClassify();
+
+ Pageable pageable = PageRequest.of(page - 1, limit);
+
+ Specification spec = (root, query, cb) -> {
+ List predicates = new ArrayList<>();
+
+ if (keywords != null && !keywords.isEmpty()) {
+ String pattern = "%" + keywords + "%";
+ Predicate namePred = cb.like(root.get("name"), pattern);
+ Predicate descPred = cb.like(root.get("description"), pattern);
+ Predicate tagPred = cb.like(root.get("tag"), pattern);
+ predicates.add(cb.or(namePred, descPred, tagPred));
+ }
+
+ if (!"all".equalsIgnoreCase(classify)) {
+ predicates.add(cb.equal(root.get("classify"), classify));
+ }
+
+ return cb.and(predicates.toArray(new Predicate[0]));
+ };
+
+ Page pageResult = applicationRepository.findAll(spec, pageable);
+
+ List items = pageResult.getContent().stream()
+ .map(app -> new ApplicationVo.ApplicationItem(
+ app.getId(),
+ app.getName(),
+ app.getDescription(),
+ app.parseTagSafely(),
+ app.getIcon(),
+ app.getClassify()
+ ))
+ .toList();
+
+ return new ApplicationVo(items, pageResult.getTotalElements());
+ }
+
+ // 创建应用(支持 isInit)
+ @Transactional
+ public Application createApplication(@Valid CreateApplicationDto dto, boolean isInit) {
+ String name = dto.getName();
+ Application existing = applicationRepository.findByName(name);
+ Locale locale = LocaleContextHolder.getLocale();
+
+ if (isInit && existing != null) {
+ return existing;
+ }
+
+ if (!isInit && existing != null) {
+ String msg = messageSource.getMessage(
+ "exception.applicationInfo.exists",
+ new Object[]{name},
+ "应用已存在",
+ locale
+ );
+ throw new ResponseStatusException(HttpStatus.BAD_REQUEST, msg);
+ }
+
+ Application newApp = new Application(
+ dto.getName(),
+ dto.getDescription(),
+ dto.getTag(),
+ dto.getIcon(),
+ dto.getClassify()
+ );
+ return applicationRepository.save(newApp);
+ }
+}
diff --git a/template/springboot/src/main/resources/application.properties b/template/springboot/src/main/resources/application.properties
index 235c5bef..e1b8e203 100644
--- a/template/springboot/src/main/resources/application.properties
+++ b/template/springboot/src/main/resources/application.properties
@@ -1,5 +1,5 @@
-server.port=3000
-spring.datasource.url=jdbc:mysql://localhost:3306/login?allowMultiQueries=true&serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=utf8&autoReconnect=true&allowMultiQueries=true&allowPublicKeyRetrieval=true&useSSL=false
+server.port=3001
+spring.datasource.url=jdbc:mysql://localhost:3306/tiny_pro?allowMultiQueries=true&serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=utf8&autoReconnect=true&allowMultiQueries=true&allowPublicKeyRetrieval=true&useSSL=false
spring.datasource.username=root
spring.datasource.password=111111
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
@@ -24,4 +24,5 @@ jwt.secret=0Zi4SA==
# Redis 配置
spring.data.redis.host=localhost
spring.data.redis.port=6379
-reject.start=false
\ No newline at end of file
+spring.data.redis.password=000000
+reject.start=false
diff --git a/template/springboot/src/main/resources/i18n/messages_zh_CN.properties b/template/springboot/src/main/resources/i18n/messages_zh_CN.properties
index 499c6778..b6bdcfc6 100644
--- a/template/springboot/src/main/resources/i18n/messages_zh_CN.properties
+++ b/template/springboot/src/main/resources/i18n/messages_zh_CN.properties
@@ -1,134 +1,139 @@
# General
-hello=你好 {0}
+hello=\u4F60\u597D {0}
code=zh-CN
-yes=是
-no=否
+yes=\u662F
+no=\u5426
# Menu
-menu.board=看板
-menu.home=监控页
-menu.work=工作台
-menu.list=列表页
-menu.result=结果页
-menu.exception=异常页
-menu.form=表单页
-menu.profile=详情页
-menu.profile.detail=基础详情页
-menu.visualization=数据可视化
-menu.menuPage=菜单页
-menu.menuPage.second=二级菜单
-menu.menuPage.third=菜单demo页
-menu.user=个人中心
-menu.systemManager=系统管理
-menu.userManager=用户管理
-menu.userManager.info=查看用户
-menu.userManager.setting=修改信息
-menu.userManager.useradd=添加用户
-menu.permission=权限管理
-menu.permission.info=查看权限
-menu.permission.setting=修改权限
-menu.permission.permissionAdd=添加权限
-menu.role=角色管理
-menu.role.info=查看角色
-menu.menu=菜单管理
-menu.menu.info=查看菜单
-navbar.docs=文档中心
-navbar.action.locale=切换为中文
+menu.board=\u770B\u677F
+menu.home=\u76D1\u63A7\u9875
+menu.work=\u5DE5\u4F5C\u53F0
+menu.list=\u5217\u8868\u9875
+menu.result=\u7ED3\u679C\u9875
+menu.exception=\u5F02\u5E38\u9875
+menu.form=\u8868\u5355\u9875
+menu.profile=\u8BE6\u60C5\u9875
+menu.profile.detail=\u57FA\u7840\u8BE6\u60C5\u9875
+menu.visualization=\u6570\u636E\u53EF\u89C6\u5316
+menu.menuPage=\u83DC\u5355\u9875
+menu.menuPage.second=\u4E8C\u7EA7\u83DC\u5355
+menu.menuPage.third=\u83DC\u5355demo\u9875
+menu.user=\u4E2A\u4EBA\u4E2D\u5FC3
+menu.systemManager=\u7CFB\u7EDF\u7BA1\u7406
+menu.userManager=\u7528\u6237\u7BA1\u7406
+menu.userManager.info=\u67E5\u770B\u7528\u6237
+menu.userManager.setting=\u4FEE\u6539\u4FE1\u606F
+menu.userManager.useradd=\u6DFB\u52A0\u7528\u6237
+menu.permission=\u6743\u9650\u7BA1\u7406
+menu.permission.info=\u67E5\u770B\u6743\u9650
+menu.permission.setting=\u4FEE\u6539\u6743\u9650
+menu.permission.permissionAdd=\u6DFB\u52A0\u6743\u9650
+menu.role=\u89D2\u8272\u7BA1\u7406
+menu.role.info=\u67E5\u770B\u89D2\u8272
+menu.menu=\u83DC\u5355\u7BA1\u7406
+menu.menu.info=\u67E5\u770B\u83DC\u5355
+navbar.docs=\u6587\u6863\u4E2D\u5FC3
+navbar.action.locale=\u5207\u6362\u4E3A\u4E2D\u6587
# Messages
-messageBox.switchRoles=切换角色
-messageBox.userCenter=用户中心
-messageBox.userSettings=用户设置
-messageBox.logout=退出登录
-messageBox.updatePwd=修改密码
-message.delete.success=删除成功
+messageBox.switchRoles=\u5207\u6362\u89D2\u8272
+messageBox.userCenter=\u7528\u6237\u4E2D\u5FC3
+messageBox.userSettings=\u7528\u6237\u8BBE\u7F6E
+messageBox.logout=\u9000\u51FA\u767B\u5F55
+messageBox.updatePwd=\u4FEE\u6539\u5BC6\u7801
+message.delete.success=\u5220\u9664\u6210\u529F
# Cloud
-menu.cloud=云服务能力展示
-menu.btn.confirm=确认
-menu.i18n=国际化管理
+menu.cloud=\u4E91\u670D\u52A1\u80FD\u529B\u5C55\u793A
+menu.btn.confirm=\u786E\u8BA4
+menu.i18n=\u56FD\u9645\u5316\u7BA1\u7406
# Theme
-theme.title.main=个性化配置
-theme.title.first=主题
-theme.title.default=默认主题
-theme.title.honey=蜜糖主题
-theme.title.violet=紫罗兰主题
-theme.title.deepness=深邃夜空主题
-theme.title.deep=深色主题
-theme.title.light=浅色主题
-theme.title.customization=自定义主题
-theme-title-recommend=推荐主题
-theme-text-default=科技、探索、钻研、精尖、包容
-theme-text-honey=明快、感性、温暖、积极、活力
-theme-text-violet=优雅、浪漫、温柔、神秘、高贵
-theme-text-deepness=平稳、中性、空间、力量、坚硬
-theme-text-dark=深沉、果断、勇敢、坚韧、向往
+theme.title.main=\u4E2A\u6027\u5316\u914D\u7F6E
+theme.title.first=\u4E3B\u9898
+theme.title.default=\u9ED8\u8BA4\u4E3B\u9898
+theme.title.honey=\u871C\u7CD6\u4E3B\u9898
+theme.title.violet=\u7D2B\u7F57\u5170\u4E3B\u9898
+theme.title.deepness=\u6DF1\u9083\u591C\u7A7A\u4E3B\u9898
+theme.title.deep=\u6DF1\u8272\u4E3B\u9898
+theme.title.light=\u6D45\u8272\u4E3B\u9898
+theme.title.customization=\u81EA\u5B9A\u4E49\u4E3B\u9898
+theme-title-recommend=\u63A8\u8350\u4E3B\u9898
+theme-text-default=\u79D1\u6280\u3001\u63A2\u7D22\u3001\u94BB\u7814\u3001\u7CBE\u5C16\u3001\u5305\u5BB9
+theme-text-honey=\u660E\u5FEB\u3001\u611F\u6027\u3001\u6E29\u6696\u3001\u79EF\u6781\u3001\u6D3B\u529B
+theme-text-violet=\u4F18\u96C5\u3001\u6D6A\u6F2B\u3001\u6E29\u67D4\u3001\u795E\u79D8\u3001\u9AD8\u8D35
+theme-text-deepness=\u5E73\u7A33\u3001\u4E2D\u6027\u3001\u7A7A\u95F4\u3001\u529B\u91CF\u3001\u575A\u786C
+theme-text-dark=\u6DF1\u6C89\u3001\u679C\u65AD\u3001\u52C7\u6562\u3001\u575A\u97E7\u3001\u5411\u5F80
# Settings
-settings.title=页面配置
-settings.themeColor=主题色
-settings.content=内容区域
-settings.search=搜索
-settings.language=语言
-settings.navbar=简约模式
-settings.menuWidth=菜单宽度 (px)
-settings.navbar.alerts=消息通知
-settings.navbar.help=帮助中心
-settings.menu=经典模式
-settings.tabBar=多页签
-settings.footer=时尚模式
-settings.colorWeek=主题配置
-settings.alertContent=配置之后仅是临时生效,要想真正作用于项目,点击下方的 "复制配置" 按钮,将配置替换到 settings.json 中即可。
-settings.copySettings=复制配置
-settings.copySettings.message=复制成功,请粘贴到 src/settings.json 文件中
-settings.close=关闭
-settings.color.tooltip=根据主题颜色生成的 10 个梯度色(将配置复制到项目中,主题色才能对亮色 / 暗黑模式同时生效
+settings.title=\u9875\u9762\u914D\u7F6E
+settings.themeColor=\u4E3B\u9898\u8272
+settings.content=\u5185\u5BB9\u533A\u57DF
+settings.search=\u641C\u7D22
+settings.language=\u8BED\u8A00
+settings.navbar=\u7B80\u7EA6\u6A21\u5F0F
+settings.menuWidth=\u83DC\u5355\u5BBD\u5EA6 (px)
+settings.navbar.alerts=\u6D88\u606F\u901A\u77E5
+settings.navbar.help=\u5E2E\u52A9\u4E2D\u5FC3
+settings.menu=\u7ECF\u5178\u6A21\u5F0F
+settings.tabBar=\u591A\u9875\u7B7E
+settings.footer=\u65F6\u5C1A\u6A21\u5F0F
+settings.colorWeek=\u4E3B\u9898\u914D\u7F6E
+settings.alertContent=\u914D\u7F6E\u4E4B\u540E\u4EC5\u662F\u4E34\u65F6\u751F\u6548\uFF0C\u8981\u60F3\u771F\u6B63\u4F5C\u7528\u4E8E\u9879\u76EE\uFF0C\u70B9\u51FB\u4E0B\u65B9\u7684 "\u590D\u5236\u914D\u7F6E" \u6309\u94AE\uFF0C\u5C06\u914D\u7F6E\u66FF\u6362\u5230 settings.json \u4E2D\u5373\u53EF\u3002
+settings.copySettings=\u590D\u5236\u914D\u7F6E
+settings.copySettings.message=\u590D\u5236\u6210\u529F\uFF0C\u8BF7\u7C98\u8D34\u5230 src/settings.json \u6587\u4EF6\u4E2D
+settings.close=\u5173\u95ED
+settings.color.tooltip=\u6839\u636E\u4E3B\u9898\u989C\u8272\u751F\u6210\u7684 10 \u4E2A\u68AF\u5EA6\u8272\uFF08\u5C06\u914D\u7F6E\u590D\u5236\u5230\u9879\u76EE\u4E2D\uFF0C\u4E3B\u9898\u8272\u624D\u80FD\u5BF9\u4EAE\u8272 / \u6697\u9ED1\u6A21\u5F0F\u540C\u65F6\u751F\u6548
# Common
-common.unauth=未授权
-common.tokenError=异常token
-common.tokenExpire=token 过期
-common.forbidden=没有 `{0}` 权限,请联系管理员
+common.unauth=\u672A\u6388\u6743
+common.tokenError=\u5F02\u5E38token
+common.tokenExpire=token \u8FC7\u671F
+common.forbidden=\u6CA1\u6709 `{0}` \u6743\u9650\uFF0C\u8BF7\u8054\u7CFB\u7BA1\u7406\u5458
# User
-user.oldPasswordError=旧密码错误
-user.requiredFieldsMissing=缺少必填字段;
-user.userNotFound=用户不存在
-user.userExists=用户已存在
-user.userNumberNull=当前为最后一个用户,禁止删除
+user.oldPasswordError=\u65E7\u5BC6\u7801\u9519\u8BEF
+user.requiredFieldsMissing=\u7F3A\u5C11\u5FC5\u586B\u5B57\u6BB5;
+user.userNotFound=\u7528\u6237\u4E0D\u5B58\u5728
+user.userExists=\u7528\u6237\u5DF2\u5B58\u5728
+user.userNumberNull=\u5F53\u524D\u4E3A\u6700\u540E\u4E00\u4E2A\u7528\u6237\uFF0C\u7981\u6B62\u5220\u9664
# Role
-role.exists=角色已存在
-role.notExists=角色不存在
-role.conflict=角色冲突,请联系管理员
+role.exists=\u89D2\u8272\u5DF2\u5B58\u5728
+role.notExists=\u89D2\u8272\u4E0D\u5B58\u5728
+role.conflict=\u89D2\u8272\u51B2\u7A81\uFF0C\u8BF7\u8054\u7CFB\u7BA1\u7406\u5458
# Permission
-permission.exists=权限 {0} 已存在
-permission.notExists=权限不存在
+permission.exists=\u6743\u9650 {0} \u5DF2\u5B58\u5728
+permission.notExists=\u6743\u9650\u4E0D\u5B58\u5728
# Menu
-menu.exists=菜单 {0} 已存在
-menu.notExists=菜单不存在
+menu.exists=\u83DC\u5355 {0} \u5DF2\u5B58\u5728
+menu.notExists=\u83DC\u5355\u4E0D\u5B58\u5728
# Lang
-lang.notExists={0} 不存在
-lang.exists={0} 已存在
-lang.notExistsCommon=语言不存在
-lang.DELETE_LANG_CONFLICT=请先清空{0}下的所有国际化词条
+lang.notExists={0} \u4E0D\u5B58\u5728
+lang.exists={0} \u5DF2\u5B58\u5728
+lang.notExistsCommon=\u8BED\u8A00\u4E0D\u5B58\u5728
+lang.DELETE_LANG_CONFLICT=\u8BF7\u5148\u6E05\u7A7A{0}\u4E0B\u7684\u6240\u6709\u56FD\u9645\u5316\u8BCD\u6761
# I18
-i18.exists=国际化词条已存在
-i18.notExists=国际化词条不存在
+i18.exists=\u56FD\u9645\u5316\u8BCD\u6761\u5DF2\u5B58\u5728
+i18.notExists=\u56FD\u9645\u5316\u8BCD\u6761\u4E0D\u5B58\u5728
# Auth
-auth.userNotExists=用户不存在
-auth.passwordOrEmailError=账号或密码错误
+auth.userNotExists=\u7528\u6237\u4E0D\u5B58\u5728
+auth.passwordOrEmailError=\u8D26\u53F7\u6216\u5BC6\u7801\u9519\u8BEF
# Preview
-preview.reject-this-request=服务器拒绝了本次请求, 因为演示模式下不允许增加, 删除, 修改数据
+preview.reject-this-request=\u670D\u52A1\u5668\u62D2\u7EDD\u4E86\u672C\u6B21\u8BF7\u6C42, \u56E0\u4E3A\u6F14\u793A\u6A21\u5F0F\u4E0B\u4E0D\u5141\u8BB8\u589E\u52A0, \u5220\u9664, \u4FEE\u6539\u6570\u636E
# Validations
-NOT_EMPTY={0} 不能为空
-IS_ARRAY={0} 必须是数组
-NOT_EMPTY_HUMAN={0} 不能为空
+NOT_EMPTY={0} \u4E0D\u80FD\u4E3A\u7A7A
+IS_ARRAY={0} \u5FC5\u987B\u662F\u6570\u7EC4
+NOT_EMPTY_HUMAN={0} \u4E0D\u80FD\u4E3A\u7A7A
+
+page=\u9875\u7801
+limit=\u6BCF\u9875\u6761\u6570
+jakarta.validation.constraints.Min.message={page}\u4E0D\u80FD\u5C0F\u4E8E{value}
+jakarta.validation.constraints.Max.message={limit}\u4E0D\u80FD\u5927\u4E8E{value}
From b121585f3e8a02c0457b07b539526af6b6760bba Mon Sep 17 00:00:00 2001
From: msslulu <1484036491@qq.com>
Date: Wed, 26 Aug 2026 19:47:16 -0700
Subject: [PATCH 2/4] =?UTF-8?q?springboot=20=E5=90=8E=E7=AB=AF?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../java/com/TinyPro/DataInitializer.java | 181 +++++++++++++++---
.../java/com/TinyPro/TinyProApplication.java | 3 +-
.../com/TinyPro/config/TinyProProperties.java | 154 +++++++++++++++
.../controller/ApplicationController.java | 8 +-
.../entity/dto/CreateApplicationDto.java | 5 +-
.../entity/dto/PaginationQueryDto.java | 25 ++-
.../com/TinyPro/entity/po/Application.java | 8 +-
.../main/java/com/TinyPro/entity/po/Menu.java | 3 +-
.../com/TinyPro/entity/po/Permission.java | 3 +-
.../exception/GlobalExceptionHandler.java | 122 ++++++++----
.../com/TinyPro/filter/RejectInterceptor.java | 18 +-
.../TinyPro/jpa/IPermissionRepository.java | 2 +
.../java/com/TinyPro/jpa/IRoleRepository.java | 1 +
.../java/com/TinyPro/jpa/IUserRepository.java | 1 +
.../TinyPro/logging/MaskingPatternLayout.java | 61 ++++++
.../service/imp/ApplicationServiceImpl.java | 26 ++-
.../main/java/com/TinyPro/utils/JwtUtil.java | 17 +-
.../src/main/resources/application.properties | 20 +-
.../src/main/resources/logback-spring.xml | 40 ++++
.../TinyPro/config/TinyProPropertiesTest.java | 30 +++
.../controller/ApplicationControllerTest.java | 49 +++++
.../exception/GlobalExceptionHandlerTest.java | 37 ++++
.../logging/MaskingPatternLayoutTest.java | 26 +++
.../imp/ApplicationServiceImplTest.java | 59 ++++++
24 files changed, 799 insertions(+), 100 deletions(-)
create mode 100644 template/springboot/src/main/java/com/TinyPro/config/TinyProProperties.java
create mode 100644 template/springboot/src/main/java/com/TinyPro/logging/MaskingPatternLayout.java
create mode 100644 template/springboot/src/main/resources/logback-spring.xml
create mode 100644 template/springboot/src/test/java/com/TinyPro/config/TinyProPropertiesTest.java
create mode 100644 template/springboot/src/test/java/com/TinyPro/controller/ApplicationControllerTest.java
create mode 100644 template/springboot/src/test/java/com/TinyPro/exception/GlobalExceptionHandlerTest.java
create mode 100644 template/springboot/src/test/java/com/TinyPro/logging/MaskingPatternLayoutTest.java
create mode 100644 template/springboot/src/test/java/com/TinyPro/service/imp/ApplicationServiceImplTest.java
diff --git a/template/springboot/src/main/java/com/TinyPro/DataInitializer.java b/template/springboot/src/main/java/com/TinyPro/DataInitializer.java
index ab22b6cc..11c4cffb 100644
--- a/template/springboot/src/main/java/com/TinyPro/DataInitializer.java
+++ b/template/springboot/src/main/java/com/TinyPro/DataInitializer.java
@@ -51,6 +51,8 @@ public class DataInitializer implements CommandLineRunner {
private LangRepository langRepository;
@Autowired
private I18Repository i18Repository;
+ @Autowired
+ private ApplicationRepository applicationRepository;
@Override
public void run(String... args) throws Exception {
@@ -68,6 +70,9 @@ public void run(String... args) throws Exception {
// 初始化国际化信息
initI18n();
+ // 初始化应用
+ initApplications();
+
// 初始化权限
initPermissions();
@@ -90,9 +95,17 @@ private void initI18n() throws IOException {
try (InputStream is = pathResource.getInputStream()) {
String json = new String(is.readAllBytes(), StandardCharsets.UTF_8);
Map> i18nData = JSON.parseObject(json, Map.class);
+ Set existingLangNames = langRepository.findAll()
+ .stream()
+ .map(Lang::getName)
+ .collect(Collectors.toSet());
// 遍历外层 Map
for (Map.Entry> outerEntry : i18nData.entrySet()) {
String langName = outerEntry.getKey(); // 外层键作为 Lang 的 name
+ if (existingLangNames.contains(langName)) {
+ logger.info("语言 {} 已存在,跳过初始化", langName);
+ continue;
+ }
Lang lang = new Lang();
lang.setName(langName);
List i18List = new ArrayList<>();
@@ -123,6 +136,104 @@ private void initI18n() throws IOException {
}
}
+ private void initApplications() {
+ List applicationData = List.of(
+ new Application(
+ "Tiny Design 设计体系",
+ "华为云产品和服务的规范体系,包括交互视觉设计、业务流程、国际化、术语词条。",
+ "[{ \"type\": \"\", \"value\": \"机会点定义\" }, { \"type\": \"danger\", \"value\": \"交互设计\" }]",
+ "card-list-application-default.png",
+ "design"
+ ),
+ new Application(
+ "Tiny DesignLink 设计流水线工具",
+ "设计+协同+资源管理,一个工具就够了,在线原型设计、设计过程融入DevOps流程。",
+ "[{ \"type\": \"error\", \"value\": \"交互设计\" }, { \"type\": \"warning\", \"value\": \"视觉设计\" }]",
+ "card-list-application-default.png",
+ "design"
+ ),
+ new Application(
+ "TinyUI3.0 开发工具 ",
+ "Cloud Design System 提供了丰富的规范文档及开发组件。",
+ "[{ \"type\": \"success\", \"value\": \"开发\" }]",
+ "card-list-application-default.png",
+ "dev"
+ ),
+ new Application(
+ "TinyPlus3.0 开发工具",
+ "TinyPlus3.0 是基于Angular + Typescript的Web前端云业务组件库。",
+ "[{ \"type\": \"success\", \"value\": \"开发\" }]",
+ "card-list-tiny-plus.png",
+ "dev"
+ ),
+ new Application(
+ "Tiny Stage 工程工具 ",
+ "一个跨平台的前端工程化cli工具,为开发提供一系列开发套件和工程插件",
+ "[{ \"type\": \"success\", \"value\": \"开发\" }]",
+ "card-list-console-framework.png",
+ "dev"
+ ),
+ new Application(
+ "Tiny Flow 接口编排工具 ",
+ "端到端的API编排解决方案,通过可视化编程的方式快速生成、发布、调试的API编排。",
+ "[{ \"type\": \"success\", \"value\": \"开发\" }]",
+ "card-list-console-framework.png",
+ "dev"
+ ),
+ new Application(
+ "Tiny Gate 门禁系统",
+ "门禁系统,通过卡点方式集成到伏羲流水线,在服务发布时生成预览页面。",
+ "[{ \"type\": \"info\", \"value\": \"测试验证\" }]",
+ "card-list-console-framework.png",
+ "dev"
+ ),
+ new Application(
+ "Console Framework 控制台框架",
+ "华为云各服务快速构建管理控制台的平台。",
+ "[{ \"type\": \"success\", \"value\": \"开发\" },{ \"type\": \"info\", \"value\": \"测试验证\" },{ \"type\": \"warning\", \"value\": \"上线\" }]",
+ "card-list-console-framework.png",
+ "dev"
+ ),
+ new Application(
+ "Nodejs Framework Nodejs应用",
+ "基于egg的定制化web服务框架,让你快速上手Nodejs做BFF意见微服务。",
+ "[{ \"type\": \"success\", \"value\": \"开发\" },{ \"type\": \"info\", \"value\": \"测试验证\" }]",
+ "card-list-console-framework.png",
+ "dev"
+ ),
+ new Application(
+ "Furion 前端体验监控",
+ "提供端到端前端用户体验度量,让产品用户体验可度量、可监控、可优化。",
+ "[{ \"type\": \"\", \"value\": \"机会点定义\" }]",
+ "card-list-furion.png",
+ "dev"
+ ),
+ new Application(
+ "Tiny Mock API 管理",
+ "功能强大的API管理平台,旨在为开发、产品、测试人员提供更优雅的接口管理服务。",
+ "[{ \"type\": \"success\", \"value\": \"开发\" },{ \"type\": \"warning\", \"value\": \"视觉设计\" }]",
+ "card-list-application-default.png",
+ "dev"
+ )
+ );
+
+ Set existingAppNames = applicationRepository.findAll()
+ .stream()
+ .map(Application::getName)
+ .collect(Collectors.toSet());
+ List newApplications = applicationData.stream()
+ .filter(app -> !existingAppNames.contains(app.getName()))
+ .collect(Collectors.toList());
+
+ if (newApplications.isEmpty()) {
+ logger.info("没有新应用需要导入,数据库已存在所有应用");
+ return;
+ }
+
+ applicationRepository.saveAll(newApplications);
+ logger.info("成功导入 {} 个新应用", newApplications.size());
+ }
+
private void initPermissions() {
Map permissions = new HashMap<>();
permissions.put("user", new String[]{"add", "remove", "update", "query", "password::force-update","batch-remove"});
@@ -132,35 +243,34 @@ private void initPermissions() {
permissions.put("i18n", new String[]{"add", "remove", "update", "query","batch-remove"});
permissions.put("lang", new String[]{"add", "remove", "update", "query"});
- Permission superPermission = new Permission();
- superPermission.setName("*");
- superPermission.setDesc("super permission");
- try {
- permissionRepository.save(superPermission);
- } catch (Exception e) {
- logger.error(e.getMessage());
- logger.error("Please clear the database and try again");
- System.exit(-1);
- }
+ createPermissionIfAbsent("*", "super permission");
for (Map.Entry entry : permissions.entrySet()) {
String module = entry.getKey();
String[] actions = entry.getValue();
for (String action : actions) {
- Permission permission = new Permission();
- permission.setName(module + "::" + action);
- permission.setDesc("");
- try {
- permissionRepository.save(permission);
- } catch (Exception e) {
- logger.error(e.getMessage());
- logger.error("Please clear the database and try again");
- System.exit(-1);
- }
+ createPermissionIfAbsent(module + "::" + action, "");
}
}
}
+ private void createPermissionIfAbsent(String name, String desc) {
+ if (permissionRepository.existsByName(name)) {
+ return;
+ }
+
+ Permission permission = new Permission();
+ permission.setName(name);
+ permission.setDesc(desc);
+ try {
+ permissionRepository.save(permission);
+ } catch (Exception e) {
+ logger.error(e.getMessage());
+ logger.error("Please clear the database and try again");
+ System.exit(-1);
+ }
+ }
+
private void initMenus() throws IOException {
List menuData = getMenuData();
try {
@@ -222,14 +332,21 @@ private List getMenuData() throws IOException {
}
private Role initRole() {
- Role role = new Role();
- role.setName("admin");
+ Role role = roleRepository.findAllByName(Contants.ADMIN)
+ .stream()
+ .findFirst()
+ .orElseGet(() -> {
+ Role newRole = new Role();
+ newRole.setName(Contants.ADMIN);
+ return newRole;
+ });
- Set permissions = permissionRepository.findByDesc("super permission")
+ Permission superPermission = permissionRepository.findAllByName("*")
.stream()
- .collect(Collectors.toSet()); // Java 17+ 不可变列表
+ .findFirst()
+ .orElseThrow(() -> new IllegalStateException("super permission not initialized"));
- role.setPermission(permissions);
+ role.setPermission(new HashSet<>(List.of(superPermission)));
Set