# fg **Repository Path**: acircumstance/fg ## Basic Information - **Project Name**: fg - **Description**: 家庭谱系微服务 - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-01-30 - **Last Updated**: 2026-05-08 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # FG 族谱系统 [![Spring Boot](https://img.shields.io/badge/Spring%20Boot-3.3.13-brightgreen.svg)](https://spring.io/projects/spring-boot) [![Java](https://img.shields.io/badge/Java-17-orange.svg)](https://www.oracle.com/java/) [![Spring Cloud Alibaba](https://img.shields.io/badge/Spring%20Cloud%20Alibaba-2023.0.3.3-blue.svg)](https://spring.io/projects/spring-cloud-alibaba) 一个严格遵循 DDD + SOLID 设计原则的族谱社交平台,展现现代微服务架构的最佳实践。 --- ## 一、SOLID 设计原则 ### 1. S - 单一职责原则 (Single Responsibility) 每层每类各司其职,职责边界清晰: | 类别 | 职责 | 代码位置 | |------|------|----------| | **Controller** | 接收请求、参数校验、返回响应 | `fg-core-adapter-web` | | **Service** | 编排业务流程、事务边界 | `fg-core-application` | | **Entity** | 领域业务逻辑、状态变更 | `fg-core-domain` | | **Repository** | 数据访问抽象 | `fg-core-domain` (接口) → `fg-core-infra` (实现) | | **Converter** | Entity ↔ PO 转换 | `fg-core-infra` | | **PO** | 数据库表映射 | `fg-core-infra` | ```java // Controller - 仅负责接收请求 @RestController public class MessageController { private final MessageService messageService; @PostMapping("/send") public RespBody send(@Valid @RequestBody SendMessageReq req) { return RespBody.success(messageService.sendMessage(req)); } } // Converter - 仅负责对象转换 @Component public class MessageConverter { public Message toEntity(MessagePO po) { return Message.rebuild().id(po.getId()).content(po.getContent()).build(); } } // Entity - 仅承载业务逻辑 @Getter public class Message { public void revoke() { if (!canRevoke()) throw new IllegalStateException("超过撤回时间限制(2分钟)"); this.isRevoked = 1; this.revokeTime = LocalDateTime.now(); } } ``` --- ### 2. O - 开放封闭原则 (Open/Closed) 对扩展开放,对修改封闭: **策略模式 - 存储适配器** ```java // 接口抽象 public interface StorageAdapter { void uploadFile(MultipartFile file, String bucket, String objectName); InputStream downLoad(String bucket, String objectName); } // MinIO 实现 public class MinioStorageAdapter implements StorageAdapter { ... } // 阿里云 OSS 实现 public class AliStorageAdapter implements StorageAdapter { ... } // 配置选择 - 新增存储类型只需新增实现类 @Bean public StorageAdapter setAdapter() { return "minio".equals(storageType) ? new MinioStorageAdapter() : new AliStorageAdapter(); } ``` **领域事件 - 扩展不修改主流程** ```java // 发送消息发布事件 domainEventPublisher.publish(new MessageSentEvent(messageId, conversationId)); // 新增功能只需新增处理器,无需修改 MessageService @TransactionalEventListener(phase = AFTER_COMMIT) public void onMessageSent(MessageSentEvent event) { conversationRepository.updateLastMsg(event.getConversationId()); conversationMemberRepository.incrementUnreadCount(event.getConversationId()); } ``` --- ### 3. L - 里氏替换原则 (Liskov Substitution) 子类可替换父类,不影响程序正确性: ```java // Repository 接口 public interface MessageRepository { String save(Message message); Message getById(String id); } // 实现可替换:MyBatis、JPA、缓存、Mock @Repository public class MessageRepositoryImpl implements MessageRepository { ... } // Service 依赖接口,实现可随时替换 @Service public class MessageService { private final MessageRepository messageRepository; // 接口依赖 } ``` --- ### 4. I - 接口隔离原则 (Interface Segregation) 接口精简,不强迫实现不需要的方法: ```java // MemberRepository - 仅包含成员相关操作 public interface MemberRepository { String save(Member member); Member getById(String id); Member getByIdCardNo(String idCardNo); List getByIds(List ids); } // MessageRepository - 仅包含消息相关操作 public interface MessageRepository { String save(Message message); void revoke(String id, String memberId); List listByConversationId(String conversationId); } ``` --- ### 5. D - 依赖倒置原则 (Dependency Inversion) 高层模块不依赖低层模块,都依赖抽象: ``` ┌─────────────────────────────────────────────┐ │ fg-core-domain (高层) │ │ 定义 Repository / Gateway 接口 │ │ Entity 不依赖任何外部框架 │ └─────────────────────────────────────────────┘ │ 依赖 ▼ ┌─────────────────────────────────────────────┐ │ fg-core-infra (低层) │ │ 实现 Repository / Gateway 接口 │ │ 依赖 MyBatis-Plus / WebSocket │ └─────────────────────────────────────────────┘ ``` ```java // Domain 层定义接口 package com.college.domain.repository; public interface MessageRepository { ... } // Infra 层实现接口 package com.college.infra.repository; @Repository public class MessageRepositoryImpl implements MessageRepository { ... } // Service 依赖接口而非实现 @Service public class MessageService { private final MessageRepository messageRepository; // 接口 private final MessagePushGateway messagePushGateway; // 接口 } ``` --- ## 二、DDD 架构设计 ### 1. 四层架构 ``` ┌─────────────────────────────────────────────────────────┐ │ Adapter Layer │ │ (Web / MQ / Job) ── 阻断外部技术渗透 │ └─────────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────┐ │ Application Layer │ │ Service ── 编排业务流程,事务边界 │ └─────────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────┐ │ Domain Layer │ │ Entity / VO / Aggregate / DomainService ── 核心逻辑 │ └─────────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────┐ │ Infrastructure Layer │ │ RepositoryImpl / Converter / Gateway ── 技术实现 │ └─────────────────────────────────────────────────────────┘ ``` **设计原则**:Domain 层零框架依赖,业务逻辑与技术实现完全解耦。 --- ### 2. 充血模型 Entity ```java @Getter public class Member { private String id; private String name; private Gender gender; private Member() {} // 私有构造器,禁止外部直接 new // 工厂方法 - 业务语义明确 public static Member createRegistered(String idCardNo, String name, Gender gender) { Member member = new Member(); member.idCardNo = idCardNo; member.birthDate = extractBirthDateFromIdCardNo(idCardNo); // 业务规则内聚 return member; } public static Member createUnregistered(String name, Gender gender) { ... } // 业务方法 - 无 public setter,状态变更通过行为 public void markDeceased(BigDecimal latitude, BigDecimal longitude) { this.deathDate = LocalDate.now(); this.deathLatitude = latitude; this.deathLongitude = longitude; } public void revive() { this.deathDate = null; this.deathLatitude = null; this.deathLongitude = null; } // 内部 RebuildBuilder - 仅 Infrastructure 层使用 public static RebuildBuilder rebuild() { return new RebuildBuilder(); } } ``` **设计亮点**: - 私有构造器 + 工厂方法,创建语义化 - 无 public setter,状态变更通过业务方法 - RebuildBuilder 内部类,数据库重建与业务创建分离 --- ### 3. 值对象 Value Object ```java public final class Gender { public static final Gender MALE = new Gender("1", "男"); public static final Gender FEMALE = new Gender("0", "女"); private final String code; // 不可变 private final String description; // 业务行为下沉到值对象 public Gender inferFromRelation(RelationType type) { return type.isFather() ? MALE : FEMALE; } public boolean isMale() { return this == MALE; } } ``` **设计亮点**:不可变 + 静态常量 + 业务行为,避免散弹枪修改。 --- ### 4. 聚合根 Aggregate Root ```java @Getter public class FamilyMemberAggregate { private final String memberId; // 聚合根 ID private Member member; // 聚合内实体 private FamilyRelation familyRelation; // 工厂方法 public static FamilyMemberAggregate create(Member member) { ... } public static FamilyMemberAggregate rebuild(Member member, FamilyRelation relation) { ... } // 聚合根业务方法 - 保证一致性边界 public void bindParent(Member parent, RelationType type) { this.familyRelation = FamilyRelation.create(memberId, parent.getId(), type); } public void addChild(FamilyMemberAggregate child) { // 聚合内一致性校验 if (!this.member.getId().equals(child.familyRelation.getParentId())) { throw new IllegalArgumentException("非本聚合成员"); } } } ``` **设计亮点**:聚合边界明确,Member + FamilyRelation 作为一致性单元。 --- ### 5. Repository 依赖倒置 领域层定义接口,基础设施层实现: ```java // Domain Layer - 接口定义 package com.college.domain.repository; public interface MemberRepository { String save(Member member); void update(Member member); Member getById(String id); Member getByIdCardNo(String idCardNo); } // Infrastructure Layer - 实现 package com.college.infra.repository; @Repository public class MemberRepositoryImpl implements MemberRepository { private final MemberMapper memberMapper; private final MemberConverter memberConverter; @Override public Member getById(String id) { MemberPO po = memberMapper.selectById(id); return memberConverter.toEntity(po); // PO → Entity 转换 } } ``` **设计亮点**:领域层不依赖 MyBatis-Plus,可替换持久化方案。 --- ### 6. 领域事件 Domain Events 事件驱动解耦聚合间协作: ```java // 领域事件基类 public abstract class DomainEvent { private final LocalDateTime occurredOn; private final String eventId; } // 具体事件 public class MessageSentEvent extends DomainEvent { private final String messageId; private final String conversationId; private final String fromMemberId; } public class GroupMemberAddedEvent extends DomainEvent { ... } // 事件发布接口 - Domain 层定义 public interface DomainEventPublisher { void publish(DomainEvent event); } // 事件发布实现 - Infra 层实现 @Component public class SpringDomainEventPublisher implements DomainEventPublisher { private final ApplicationEventPublisher publisher; @Override public void publish(DomainEvent event) { publisher.publishEvent(event); } } ``` **设计亮点**:聚合间通过事件通信,避免直接依赖;新增功能只需新增事件处理器。 --- ### 7. 领域服务 Domain Service 跨聚合的业务逻辑封装: ```java @Service public class FamilyMemberDomainService { // 跨聚合业务:绑定父母关系 public void bindParent(String memberId, String parentId, RelationType type) { Member member = memberRepository.getById(memberId); Member parent = memberRepository.getById(parentId); // 业务规则校验 if (type == RelationType.FATHER && member.getGender() == Gender.FEMALE) { throw new BusinessException("父亲必须为男性"); } // 创建聚合根并执行业务 FamilyMemberAggregate aggregate = FamilyMemberAggregate.rebuild(member, null); aggregate.bindParent(parent, type); // 持久化 familyRelationRepository.save(aggregate.getFamilyRelation()); } } ``` --- ## 三、设计模式应用 ### 1. 策略模式 Strategy Pattern 族谱构建采用策略模式,支持父系/母系双通道: ```java // 策略接口 public interface FamilyTreeBuilder { FamilyTreeNode buildFullTree(String memberId); String findRootAncestor(String memberId); } // 父系族谱策略 @Component public class PaternalTreeBuilder implements FamilyTreeBuilder { ... } // 母系族谱策略 @Component public class MaternalTreeBuilder implements FamilyTreeBuilder { ... } // 策略路由 @Service public class FamilyTreeDomainService { private final PaternalTreeBuilder paternalTreeBuilder; private final MaternalTreeBuilder maternalTreeBuilder; public FamilyTreeNode buildFullFamilyTree(String memberId, FamilyTypeEnum type) { FamilyTreeBuilder builder = (type == FamilyTypeEnum.MATERNAL) ? maternalTreeBuilder : paternalTreeBuilder; return builder.buildFullTree(memberId); } } ``` **设计亮点**:新增族谱类型只需添加策略实现,零侵入现有代码。 --- ### 2. 工厂模式 Factory Method Entity 工厂方法分离业务创建与数据重建: ```java // 业务创建 - 应用层调用 Member member = Member.createRegistered(idCardNo, name, gender); // 业务创建 - 未注册成员 Member member = Member.createUnregistered(name, gender); // 数据重建 - 仅 Infrastructure 层调用 Member member = Member.rebuild() .id(po.getId()) .name(po.getName()) .gender(Gender.fromCode(po.getGender())) .build(); ``` **设计亮点**:创建路径分离,业务语义明确。 --- ### 3. 规约模式 Specification Pattern 复杂查询条件封装为规约: ```java // 规约接口 public interface Specification { boolean isSatisfiedBy(T candidate); Specification and(Specification other); Specification or(Specification other); Specification not(); } // 具体规约 - 成员搜索 public class MemberSearchSpecification implements Specification { private final String keyword; private final String excludeMemberId; @Override public boolean isSatisfiedBy(Member candidate) { return candidate.getName().contains(keyword) && !candidate.getId().equals(excludeMemberId); } } // 规约组合 Specification spec = new MemberSearchSpecification("张", "123") .and(new AdultSpecification()); List result = members.stream() .filter(spec::isSatisfiedBy) .collect(Collectors.toList()); ``` **设计亮点**:查询条件可组合,业务规则复用。 --- ### 4. 模板方法模式 Template Method 抽象规约基类: ```java public abstract class AbstractSpecification implements Specification { @Override public Specification and(Specification other) { return new AndSpecification<>(this, other); } @Override public Specification or(Specification other) { return new OrSpecification<>(this, other); } @Override public Specification not() { return new NotSpecification<>(this); } } ``` --- ## 四、架构设计亮点 ### 1. 统一响应封装 泛型 RespBody,成功/失败响应统一: ```java @Data public class RespBody { private String code; private String msg; private T data; // 成功响应(无数据) public static RespBody success() { return new RespBody<>("200", "成功", null); } // 成功响应(有数据) public static RespBody success(T data) { return new RespBody<>("200", "成功", data); } // 错误响应(多种重载) public static RespBody error(RespCodeEnum code) { return new RespBody<>(code.getCode(), code.getDesc(), null); } public static RespBody error(RespCodeEnum code, String msg) { return new RespBody<>(code.getCode(), msg, null); } // 判断成功 public boolean isSuccess() { return "200".equals(this.code); } } ``` --- ### 2. 全局异常处理 覆盖多种异常类型,统一 JSON 响应: ```java @RestControllerAdvice public class GlobalExceptionHandler { // 自定义异常 @ExceptionHandler(CustomException.class) public RespBody handleCustom(CustomException ex) { log.error("Custom exception handling", ex); return RespBody.error(ex.getRespCode()); } // 参数校验异常 @ExceptionHandler(BindException.class) public RespBody handleBind(BindException ex) { String errorMsg = ex.getFieldErrors().get(0).getDefaultMessage(); return RespBody.error(RespCodeEnum.BAD_PARAM, errorMsg); } // Feign 调用异常 @ExceptionHandler(FeignException.class) public RespBody handleFeign(FeignException ex) { return RespBody.error(RespCodeEnum.THIRD_SERVICE_CALL_FAILED); } // HTTP 消息不可读 @ExceptionHandler(HttpMessageNotReadableException.class) public RespBody handleHttpMessageNotReadable(HttpMessageNotReadableException ex) { return RespBody.error(RespCodeEnum.HTTP_BAD_REQUEST); } // 全局兜底 @ExceptionHandler(Throwable.class) public RespBody handleGlobal(Throwable ex) { log.error("Global exception handling", ex); return RespBody.error(RespCodeEnum.ERROR); } } ``` --- ### 3. 响应码枚举 ```java @Getter public enum RespCodeEnum implements BaseEnum { OK("200", "成功"), ERROR("500", "系统异常"), BAD_PARAM("401", "参数错误"), BUSINESS_ERROR("402", "业务错误"), UNAUTHORIZED("415", "登录异常"), PERMISSION_DENIED("416", "权限不足"), THIRD_SERVICE_CALL_FAILED("501", "调用三方失败"); private final String code; private final String desc; } ``` --- ### 4. 枚举设计 BaseEnum 接口约束,业务方法下沉: ```java // 枚举接口 public interface BaseEnum { String getCode(); String getDesc(); } // 业务枚举实现 @Getter public enum BooleanEnum implements BaseEnum { TRUE("1", "是"), FALSE("0", "否"); private final String code; private final String desc; // 业务方法下沉 public static boolean isTrue(String code) { return TRUE.getCode().equals(code); } } @Getter public enum RelationTypeEnum implements BaseEnum { MOTHER("0", "母亲"), FATHER("1", "父亲"); // 业务方法 public boolean isFather() { return this == FATHER; } // JSON 序列化支持 @JsonCreator public static RelationTypeEnum fromCode(Object code) { ... } } ``` --- ### 5. 多模块版本管理 BOM + flatten-maven-plugin 统一版本: ```xml 1.0-SNAPSHOT 3.3.13 2023.0.6 2023.0.3.3 1.1.2.2 3.5.12 org.springframework.cloud spring-cloud-dependencies ${spring-cloud.version} pom import com.alibaba.cloud.ai spring-ai-alibaba-bom ${spring-ai-alibaba.version} pom import flatten-maven-plugin resolveCiFriendliesOnly true ``` --- ### 6. Gateway 网关设计 白名单配置 + JWT 解析 + Header 传递: ```java @Component public class AuthenticationFilter implements GlobalFilter { private static final AntPathMatcher PATH_MATCHER = new AntPathMatcher(); @Override public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { ServerHttpRequest request = exchange.getRequest(); String token = request.getHeaders().getFirst("Authorization"); String path = request.getURI().getPath(); // 白名单路径 - 可配置 if (isWhitePath(path)) { return chain.filter(exchange); } // JWT 校验 if (!JwtTokenUtil.validateToken(token)) { return errorResponse(exchange, RespCodeEnum.UNAUTHORIZED); } // 解析用户信息并通过 Header 传递给下游服务 String userId = JwtTokenUtil.getUserId(token); String userType = JwtTokenUtil.getUserType(token); ServerHttpRequest mutatedRequest = request.mutate() .header("X-User-Id", userId) .header("X-User-Type", userType) .build(); return chain.filter(exchange.mutate().request(mutatedRequest).build()); } } ``` --- ### 7. AI 服务设计 Spring AI Alibaba + 流式输出: ```java @Service public class QwenAiService { private final ChatClient chatClient; // 普通调用 public ChatSuggestionResponse generateSuggestion(ChatSuggestionRequest req) { String prompt = buildSuggestionPrompt(req); SuggestionResult result = chatClient.prompt() .user(prompt) .call() .entity(SuggestionResult.class); return ChatSuggestionResponse.builder() .suggestedReply(result.getSuggestedReply()) .build(); } // 流式输出 public Flux generateSuggestionStream(ChatSuggestionRequest req) { String prompt = buildSuggestionPrompt(req); return chatClient.prompt() .user(prompt) .stream() .content(); } } ``` --- ### 8. WebSocket 推送设计 线程安全 + 消息推送适配器: ```java @ServerEndpoint(value = "/web/socket", configurator = WebSocketServerConfig.class) @Component public class WebSocket { private static final AtomicInteger onlineCount = new AtomicInteger(0); private static final Map clients = new ConcurrentHashMap<>(); private Session session; private String memberId; @OnOpen public void onOpen(Session session, EndpointConfig conf) { String memberId = (String) conf.getUserProperties().get("memberId"); this.memberId = memberId; this.session = session; // 防止重复连接 if (clients.containsKey(memberId)) { clients.get(memberId).session.close(); onlineCount.decrementAndGet(); } clients.put(memberId, this); onlineCount.incrementAndGet(); } @OnMessage public void onMessage(String message) { if ("ping".equals(message)) { sendMessage("pong", session); // 心跳响应 } } public void sendMessage(String message, Session session) { session.getAsyncRemote().sendText(message); } } // 消息推送适配器 - 实现依赖倒置 @Component public class WebSocketMessagePusher implements MessagePusher { @Override public void pushToMember(String memberId, String message) { WebSocket socket = webSocket.getSocket(memberId); if (socket != null && socket.getSession().isOpen()) { socket.sendMessage(message, socket.getSession()); } } } ``` --- ### 9. 并发安全设计 ThreadLocal + 内存泄漏防护: ```java public class UserContext { private static final ThreadLocal CONTEXT = new ThreadLocal<>(); /** * 安全说明: * 1. 必须在请求结束时调用 clear() 方法清理,防止内存泄漏 * 2. Servlet 环境通过拦截器自动清理 * 3. Reactive 环境通过 Filter doFinally 自动清理 */ public static void set(String userId, String userType) { CONTEXT.set(new UserInfo(userId, userType)); } public static String getUserId() { UserInfo info = CONTEXT.get(); return info != null ? info.userId() : null; } public static void clear() { CONTEXT.remove(); } public record UserInfo(String userId, String userType) {} } // 拦截器自动清理 @Component public class UserContextInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, ...) { String userId = request.getHeader("X-User-Id"); if (userId != null) UserContext.set(userId, userType); return true; } @Override public void afterCompletion(HttpServletRequest request, ...) { UserContext.clear(); // 防止内存泄漏 } } ``` --- ### 10. DTO 转换器设计 分层转换 + 递归转换 + 空值处理: ```java @Component public class FamilyDtoConverter { // 转换为 API 层 DTO public FamilyTreeFullRespDto toApiDto(FamilyTreeFullResp appResp) { if (appResp == null) return null; FamilyTreeFullRespDto apiResp = new FamilyTreeFullRespDto(); apiResp.setFamilyType(appResp.getFamilyType()); if (appResp.getRoot() != null) { apiResp.setRoot(convertNode(appResp.getRoot())); } return apiResp; } // 递归转换节点(包含配偶和子节点) private FamilyTreeNodeDto convertNode(FamilyTreeNode appNode) { if (appNode == null) return null; FamilyTreeNodeDto apiNode = new FamilyTreeNodeDto(); apiNode.setMemberId(appNode.getMemberId()); apiNode.setName(appNode.getName()); // 配偶递归转换 if (appNode.getSpouse() != null) { apiNode.setSpouse(convertNode(appNode.getSpouse())); } // 子节点递归转换 if (appNode.getChildren() != null) { apiNode.setChildren(appNode.getChildren().stream() .map(this::convertNode) .collect(Collectors.toList())); } return apiNode; } } ``` --- ## 五、分层职责 | 层级 | 职责 | SOLID 原则体现 | |------|------|----------------| | **Adapter** | 阻断外部技术渗透 | S 单一职责 | | **Application** | 编排业务流程 | S 单一职责 | | **Domain** | 核心业务逻辑 | D 依赖倒置(定义接口) | | **Infrastructure** | 技术实现 | D 依赖倒置(实现接口) | --- ## 六、项目结构 ``` fg-parent/ ├── fg-api/ # DTO、枚举、常量 ├── fg-common/ # 异常、响应封装、UserContext ├── fg-repository/ # 数据访问抽象 ├── fg-service/ │ ├── fg-core/ │ │ ├── fg-core-domain/ # 领域层(Entity、VO、Aggregate、Repository接口) │ │ ├── fg-core-application/ # 应用层(Service、DTO、Converter) │ │ ├── fg-core-infra/ # 基础设施层(RepositoryImpl、Mapper、PO) │ │ ├── fg-core-adapter/ # 适配层(Web/MQ/Job) │ │ │ ├── fg-core-adapter-web/ │ │ │ ├── fg-core-adapter-mq/ │ │ │ └── fg-core-adapter-job/ │ │ └── fg-core-starter/ # 启动入口 │ ├── fg-ai/ # AI 服务(Spring AI Alibaba) │ ├── fg-gateway/ # 网关(JWT认证、白名单、Header传递) │ └── fg-oss/ # 文件存储(策略模式:MinIO/OSS) └── docs/ ``` --- ## 七、技术选型 | 类别 | 技术 | 选型理由 | |------|------|----------| | 核心框架 | Spring Boot 3.3.13 · Spring Cloud Alibaba 2023.0.3.3 | Jakarta EE 10 兼容 | | ORM | MyBatis-Plus 3.5.12 | 动态 SQL,减少样板代码 | | AI | Spring AI Alibaba 1.1.2.2 | Spring 生态原生支持 | | 工具库 | Hutool 5.8.22 · MapStruct 1.4.2 | 编译期类型转换 | | 存储 | MinIO 8.2.0 / 阿里云 OSS | 私有化与云存储双模式 | --- ## 许可证 本项目仅供学习交流使用。