Files
skills/mis-paw-spec/SKILL.md
T
V-LiuShuang 797f6e9932 add
2026-08-19 13:23:09 +08:00

445 lines
17 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
name: mis-paw-spec
description: 在mis-paw模块编写代码必备技能。
---
# Intro
请结合[目录结构](#目录结构)与[核心技术栈](#核心技术栈),并严格遵守[代码编写规范](#代码编写规范)生成代码,被要求编写HTTP接口文档时,请参考[HTTP接口文档示例](./examples/api-doc.md)。
## 核心技术栈
- JavaOpenJDK21
- spring-boot
- 版本:3.2.9
- 文档:https://docs.spring.io/spring-boot/docs/3.2.9/reference/html
- 源码:https://github.com/spring-projects/spring-boot/tree/v3.2.9
- spring-framework
- 版本:6.1.12
- 文档:https://docs.spring.io/spring-framework/reference/6.2/index.html
- 源码:https://github.com/spring-projects/spring-framework/tree/v6.1.12
- mybatis-plus
- 版本:3.5.7
- 文档:https://github.com/baomidou/mybatis-plus-doc
- 源码:https://github.com/baomidou/mybatis-plus/tree/v3.5.7
- MySQL
- 版本:8.0.25
- 文档:https://dev.mysql.com/doc/refman/8.0/en/
- 源码:https://github.com/mysql/mysql-server/tree/mysql-8.0.25
- Redis8.0+
- hutool-core
- 版本:5.8.42
- 文档:https://github.com/chinabugotech/hutool-site/tree/master/docs/core
- 源码:https://github.com/chinabugotech/hutool/tree/5.8.42/hutool-core
- hutool-json
- 版本:5.8.42
- 文档:https://github.com/chinabugotech/hutool-site/tree/master/docs/json
- 源码:https://github.com/chinabugotech/hutool/tree/5.8.42/hutool-json
- spring-cloud-alibaba
- 版本:2023.0.1.2
- 文档:https://sca.aliyun.com/docs/2023/overview/version-explain
- 源码:https://github.com/alibaba/spring-cloud-alibaba/tree/2023.0.1.2
- RocketMQ
- 文档:https://rocketmq.apache.org/zh/docs/4.x
- rocketmq-client
- 版本:4.9.8
- 源码:https://github.com/apache/rocketmq/tree/rocketmq-all-4.9.8
- rocketmq-spring-boot-starter
- 版本:2.2.3
- 源码:https://github.com/apache/rocketmq-spring/tree/rocketmq-spring-all-2.2.3
- elasticsearch
- 版本:9.2.0
- 文档:https://github.com/elastic/elasticsearch/tree/v9.2.0/docs
- 源码:https://github.com/elastic/elasticsearch/tree/v9.2.0
## 目录结构
### 项目根目录
```text
<root>\
├── mis-api\ # 微服务之间暴露的dubbo接口
├── mis-auth\ # API网关鉴权服务
├── mis-common\ # 存放多个模块的公共依赖
├── mis-gateway\ # API网关
├── mis-modules\ # 业务模块代码根目录
├── mis-visual\ # 与业务无关的子模块
└── pom.xml # 在<profiles>中定义了Nacos地址及账号密码、NewAPI的地址和密钥
```
### 模块目录结构
```text
mis-modules\mis-paw\
├── src\main\
│ ├── java\com\lcfc\mispaw\
│ │ ├── cluster # 集群相关
│ │ ├── config # 配置类
│ │ ├── constant # 常量定义
│ │ ├── controller\ # 控制器层
│ │ │ ├── agents # 智能体
│ │ │ ├── approval # 审批
│ │ │ ├── chats # 聊天
│ │ │ ├── client # 客户端
│ │ │ ├── corn # 定时任务
│ │ │ ├── export # 导出
│ │ │ ├── files # 文件预览
│ │ │ ├── instance # 管理qwenpaw实例
│ │ │ ├── mcp # 管理qwenpaw实例的MCP
│ │ │ ├── models # 模型相关
│ │ │ ├── plan # 计划模式配置
│ │ │ ├── plugins # 管理qwenpaw实例的插件
│ │ │ ├── settings # 自定义qwenpaw实例设置
│ │ │ ├── skills # 管理qwenpaw实例的Skill
│ │ │ ├── token # 查看qwenpaw实例的token消耗
│ │ │ ├── toolguard # 管理qwenpaw实例的工具护栏
│ │ │ ├── tools # 管理qwenpaw实例的工具
│ │ │ └── workspace # 管理qwenpaw实例的工作区
│ │ ├── domain\ # 存放DTO、VO
│ │ ├── dubbo # Dubbo接口实现类
│ │ ├── enums # 枚举
│ │ ├── event # Spring的ApplicationEvent子类
│ │ ├── exception # 自定义异常类
│ │ ├── filter # 过滤器
│ │ ├── handler # 处理器
│ │ ├── mapper\ # MybatisMapper接口
│ │ ├── mq # 消息队列相关
│ │ ├── service\ # 业务服务层
│ │ ├── sessionfact\ # 会话服务
│ │ ├── task # 定时任务
│ │ ├── util # 工具类
│ │ └── workflow # 工作流
│ └── resources\
│ ├── mapper\ # MybatisMapperXML
│ ├── application.yml # 配置文件
│ └── logback-plus.xml # 日志配置
└── pom.xml # 当前Maven模块的依赖与构建配置
```
## 代码编写规范
- 积极采用JDK11~21的新特性,例如:局部变量类型推断、Switch表达式、模式匹配、文本块、记录类、密封类、虚拟线程。
- 虚拟线程应当用于IO密集型的任务,而不是CPU密集型任务。
- 为了降低代码耦合度,提高可维护性,请积极的采用工厂、模板、策略、发布订阅等常用的设计模式。
- 对于发布订阅模式,请根据以下情况判断:跨服务通信、集群实例广播消息,优先使用RocketMQ消息队列。进程通信使用ApplicationEventPublisher和@EventListener即可
- 一个方法体内的行数不得超过20行,若超过20行,请拆成多个方法。
- 一个Java源文件的代码行数不得超过300行,若超过300行,说明拆的不够细,请拆成多个文件。
- 请积极优先使用Lombok注解,无需担心编译速度被拖慢。
- 对于工具类的使用,请优先采纳hutool,除非hutool没有提供才新建一个工具类。
- 对于null处理优先使用Optional而不是if,优先使用Stream而不是循环。
- 如果Stream的map代码块超过5行就单独写一个函数,如果map中代码块需要中间对象,优先使用记录类,记录类和DTO存放在一起。
- 构建JavaBean对象,优先使用构造函数而不是单独写工具类或方法,只有3个以内的属性,应提供一个全部参数的构造函数。
- List在非多线程场景,默认用ArrayList就够了。只有需要按照插入顺序遍历元素的场景,才考虑使用LinkedList。需考虑线程安全用CopyOnWriteArrayList。
- Set在非多线程场景,默认用HashSet。对排序要求考虑用LinkedHashSet或TreeSet。需考虑线程安全用`cn.hutool.core.collection.ConcurrentHashSet`、ConcurrentSkipListSet、CopyOnWriteArraySet等。
- Map在非线程场景,默认使用HashMap就够了。对排序要求考虑用LinkedHashMap或TreeMap。如果存在并发场景,需考虑线程安全用ConcurrentHashMap或ConcurrentSkipListMap等JUC工具包。
- Queue的使用原则:要控制内存,用ArrayBlockingQueue。经典生产者消费者,用LinkedBlockingQueue。要窃取任务,用LinkedBlockingDeque。追求极致吞吐且不怕队列暴涨,用ConcurrentLinkedQueue。必须双端且高吞吐,用ConcurrentLinkedDeque。
- Lock的使用原则:分布式锁使用Redisson相关API,单机使用ReentrantLock、StampedLock、ReentrantReadWriteLock即可。
## 实体类与MySQL字段类型映射规则
|实体类字段类型|MySQL字段类型|
| --- | --- |
| String | VARCHAT(255) |
| Long | BIGINT |
| Integer | INT |
| LocalDate | DATE |
| LocalDateTime | DATETIME |
| BigDecimal | DECIMAL(14, 2) |
- 数据库表结构字段名称、实体类字段名称,采用驼峰与下划线映射的方式。
- `@TableField`注解中`typeHandler`等于`JacksonTypeHandler.class`时,表结构字段默认JSON类型。
- 实体类字段包含`@NotNull`注解,需要结合`@Schema`注解中的`defaultValue`属性判断是否需要设置默认值。
- 主键列是否采用MySQL自增序列,取决于实体类被`@TableId`注解修饰的字段,要看其`type`属性,如果是`IdType.AUTO`则使用自增,否则不需要。
- 如果`@Schema`注解指定了`maxLength`属性,按如下原则设置字段类型:
- 数值小于或等于2000,使用VARCHAR
- 数值大于2000且小于10000,使用TEXT
- 数值大于10000,使用LONGTEXT
## MySQL表结构示例
```sql
CREATE TABLE `example_table` (
`id` BIGINT NOT NULL COMMENT 'ID',
`str_field` VARCHAR(32) NOT NULL COMMENT '字符串`,
`int_field` INT DEFAULT NULL COMMENT '`,
`long_field` BIGINT DEFAULT NULL COMMENT '长整数`,
`decimal_field` DECIMAL(14, 2) DEFAULT NULL COMMENT '`,
`date_field` DATE NOT NULL COMMENT '日期类型`,
`time_field` DATETIME NOT NULL COMMENT '`,
`state` VARCHAR(32) NOT NULL COMMENT '状态:only_read、read_write',
`json_field` JSON DATETIME NULL COMMENT 'JSON类型'
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='示例表';
```
## 实体类示例
```java
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import lombok.Getter;
import org.springframework.format.annotation.DateTimeFormat;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import java.io.Serial;
import java.io.Serializable;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.math.BigDecimal;
@Data
@TableName(value = "example_table", autoResultMap = true)
@Schema(title = "示例表")
public class ExampleEntity implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
@Schema(description = "ID")
@TableId(value = "id", type = IdType.ASSIGN_ID)
@TableField(value = "id")
@JsonSerialize(using = ToStringSerializer.class)
private Long id;
@NotBlank
@Schema(description = "字符串")
@TableField(value = "str_field")
private String strField;
@Schema(description = "整数", defaultValue = "0")
@TableField(value = "int_field")
private Integer intField;
@Schema(description = "长整数")
@TableField(value = "long_field")
@JsonSerialize(using = ToStringSerializer.class)
private Long longField;
@Schema(description = "高精度浮点数")
@TableField(value = "decimal_field")
private BigDecimal decimalField;
@NotNull
@Schema(description = "日期类型", pattern = "yyyy-MM-dd")
@TableField(value = "date_field")
@DateTimeFormat(pattern = "yyyy-MM-dd")
@JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
private LocalDate dateField;
@NotNull
@Schema(description = "时间类型", pattern = "yyyy-MM-dd HH:mm:ss")
@TableField(value = "time_field")
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private LocalDateTime timeField;
@NotNull
@Schema(
title = "状态",
description = "参考示例值",
example = StateEnum.DESC
)
@TableField(value = "state")
private StateEnum state;
@Schema(description = "JSON类型")
@TableField(value = "json_field", typeHandler = JacksonTypeHandler.class)
private JsonField jsonField;
@Data
public static class JsonField implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
@Schema(description = "示例字段1")
private String field1;
@Schema(description = "示例字段2")
private Integer field2;
}
}
```
## 枚举类示例
```java
import com.baomidou.mybatisplus.annotation.EnumValue;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonValue;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
@Getter
@RequiredArgsConstructor
@Schema(description = "某某状态")
public enum StateEnum {
ONLY_READ("only_read"),
READ_WRITE("read_write");
public static final String DESC = "only_read=只读、read_write=读写";
@JsonValue
@EnumValue
private final String value;
@JsonCreator
public static StateEnum deserialize(String input) {
return Arrays.stream(values()).filter(e -> e.getValue().equals(input)).findFirst().orElse(null);
}
}
```
## DTO示例
```java
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import lombok.Getter;
import org.springframework.format.annotation.DateTimeFormat;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import java.io.Serial;
import java.io.Serializable;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.math.BigDecimal;
@Data
@Schema(title = "DTO示例")
public class ExampleDTO implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
@NotBlank
@Schema(description = "字符串")
private String strField;
@Schema(description = "长整数")
@JsonSerialize(using = ToStringSerializer.class)
private Long longField;
@NotNull
@Schema(description = "日期类型", pattern = "yyyy-MM-dd")
@DateTimeFormat(pattern = "yyyy-MM-dd")
@JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
private LocalDate dateField;
@NotNull
@Schema(description = "时间类型", pattern = "yyyy-MM-dd HH:mm:ss")
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private LocalDateTime timeField;
@NotNull
@Schema(
title = "状态",
description = "参考示例值",
example = StateEnum.DESC
)
private StateEnum state;
}
```
## FAQ
### 怎么确认用哪个profile
固定使用dev的profile。
### 怎么读profile的配置?
从[项目根目录](#项目根目录)的`pom.xml`文件中获取**id=devprofiles.active=lab**的profile,例如:
```xml
<profile>
<id>dev</id>
<properties>
<profiles.active>lab</profiles.active>
<!-- 当前环境的Nacos服务地址 -->
<nacos.server>127.0.0.1:8868</nacos.server>
<!-- 当前环境的Nacos注册中心分组名称 -->
<nacos.discovery.group>DEFAULT_GROUP</nacos.discovery.group>
<!-- 当前环境的Nacos配置中心分组名称 -->
<nacos.config.group>DEFAULT_GROUP</nacos.config.group>
<!-- 当前环境的Nacos登录账号 -->
<nacos.username>nacos</nacos.username>
<!-- 当前环境的Nacos登录密码 -->
<nacos.password>1234567890</nacos.password>
<!-- 当前环境的NewAPI服务地址 -->
<newAPI.url>http://127.0.0.1:3000</newAPI.url>
<!-- 当前环境的NewAPI模型ApiKey -->
<newAPI.key>sk-xxx</newAPI.key>
</properties>
</profile>
```
### 怎么打包?
系统已安装`mvnd`用来替换`mvn`命令,在[项目根目录](#项目根目录)执行命令:`mvnd clean package -DskipTests -Pdev`**耗时约80秒**。
### 怎么读取nacos配置文件?
按步骤一步步执行:
1. 从**项目根目录**的`pom.xml`获取以下几个属性:
- `profiles.active`
- `nacos.server`
- `nacos.config.group`
- `nacos.username`
- `nacos.password`
2. 获取accessToken。
请求示例:
```bash
curl -X POST 'http://{nacos.server}/nacos/v1/auth/login' -d 'username={nacos.username}&password={nacos.password}'
```
输出示例:
```json
{"accessToken":"xxx"}
```
如果输出不包含accessToken字段视为失败,**最多重试2次,仍然失败则直接中断整个流程。**
3. 获取配置文件信息。
- `dataId`:如果要请求MySQL执行SQL使用`datasource.yml`,否则使用`mis-agent.yml`
- `accessToken`:上一步得到的`accessToken`
请求示例:
```bash
curl -X GET "http://{nacos.server}/nacos/v3/console/cs/config?dataId={dataId}&groupName={groupName}&namespaceId={profiles.active}" -H "Authorization: Bearer {accessToken}"
```
输出示例:
```json
{
"code": 0,
"message": "success",
"data": {"content": "..."}
}
```
`data.content`就是配置内容,通常是yaml格式。