init
This commit is contained in:
commit
fb4134cc27
|
@ -0,0 +1,65 @@
|
|||
HELP.md
|
||||
target/
|
||||
!.mvn/wrapper/maven-wrapper.jar
|
||||
!**/src/main/**/target/
|
||||
!**/src/test/**/target/
|
||||
logs
|
||||
docs
|
||||
|
||||
### STS ###
|
||||
.apt_generated
|
||||
.classpath
|
||||
.factorypath
|
||||
.project
|
||||
.settings
|
||||
.springBeans
|
||||
.sts4-cache
|
||||
|
||||
### IntelliJ IDEA ###
|
||||
.idea
|
||||
*.iws
|
||||
*.iml
|
||||
*.ipr
|
||||
|
||||
### NetBeans ###
|
||||
/nbproject/private/
|
||||
/nbbuild/
|
||||
/dist/
|
||||
/nbdist/
|
||||
/.nb-gradle/
|
||||
build/
|
||||
!**/src/main/**/build/
|
||||
!**/src/test/**/build/
|
||||
|
||||
### VS Code ###
|
||||
.vscode/
|
||||
|
||||
|
||||
# Logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
|
||||
node_modules
|
||||
.DS_Store
|
||||
dist
|
||||
dist-ssr
|
||||
coverage
|
||||
*.local
|
||||
|
||||
/cypress/videos/
|
||||
/cypress/screenshots/
|
||||
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
|
||||
*.tsbuildinfo
|
|
@ -0,0 +1,49 @@
|
|||
# 定义CI/CD流水线的阶段
|
||||
stages:
|
||||
- build # 第一阶段:构建应用程序
|
||||
- build-docker # 第二阶段:构建Docker镜像
|
||||
- deploy # 第三阶段:部署应用程序
|
||||
|
||||
# 定义全局变量
|
||||
variables:
|
||||
CONTAINER_NAME: "bunny-auth-server" # Docker容器名称
|
||||
DOCKER_TAG: "4.0.0" # Docker镜像标签版本
|
||||
|
||||
# 构建任务
|
||||
build-job:
|
||||
stage: build # 指定此任务属于build阶段
|
||||
script:
|
||||
# 打印编译开始信息
|
||||
- echo "Compiling the code..."
|
||||
# 使用Maven编译Java项目,跳过测试
|
||||
- mvn clean package -DskipTests
|
||||
# 打印编译完成信息
|
||||
- echo "Compile complete."
|
||||
# 从Docker Hub拉取OpenJDK基础镜像
|
||||
- docker pull openjdk:24-ea-17-jdk-oraclelinux9
|
||||
# 打印拉取完成信息
|
||||
- echo "docker pull complete."
|
||||
# 使用Dockerfile构建Docker镜像,并打上标签
|
||||
- docker build -f Dockerfile -t $CONTAINER_NAME:$DOCKER_TAG .
|
||||
# 打印构建成功信息
|
||||
- echo "Application successfully deployed."
|
||||
|
||||
# 部署任务
|
||||
deploy-job:
|
||||
stage: deploy # 指定此任务属于deploy阶段
|
||||
environment: production # 指定部署环境为production
|
||||
script:
|
||||
# 打印部署开始信息
|
||||
- echo "Deploying application..."
|
||||
# 停止正在运行的容器(如果存在),|| true确保命令失败不会中断脚本
|
||||
- docker stop $CONTAINER_NAME || true
|
||||
# 删除容器(如果存在)
|
||||
- docker rm $CONTAINER_NAME || true
|
||||
# 运行新的Docker容器
|
||||
# -d: 后台运行
|
||||
# -p: 端口映射(7070和8000)
|
||||
# --name: 容器名称
|
||||
# --restart always: 总是自动重启
|
||||
- docker run -d -p 7070:7070 -p 8000:8000 --name $CONTAINER_NAME --restart always $CONTAINER_NAME:$DOCKER_TAG
|
||||
# 打印部署成功信息
|
||||
- echo "Application successfully deployed."
|
|
@ -0,0 +1,29 @@
|
|||
FROM openjdk:24-ea-17-jdk-oraclelinux9
|
||||
LABEL maintainer="server"
|
||||
|
||||
#系统编码
|
||||
ENV LANG=C.UTF-8 LC_ALL=C.UTF-8
|
||||
|
||||
# 设置时区,构建镜像时执行的命令
|
||||
RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
|
||||
RUN echo "Asia/Shanghai" > /etc/timezone
|
||||
|
||||
# 设定工作目录
|
||||
WORKDIR /home/server
|
||||
|
||||
# 复制jar包
|
||||
COPY target/*.jar /home/server/app.jar
|
||||
|
||||
# 启动容器时的进程
|
||||
ENTRYPOINT ["java","-jar","/home/server/app.jar"]
|
||||
|
||||
#暴露 8080 端口
|
||||
EXPOSE 8080
|
||||
EXPOSE 7070
|
||||
|
||||
# 生产环境
|
||||
# mvn clean package -Pprod -DskipTests
|
||||
|
||||
# 测试环境
|
||||
# mvn clean package -Ptest -DskipTests
|
||||
|
|
@ -0,0 +1,256 @@
|
|||
# Gitlab安装
|
||||
|
||||
完全笔记《安装GitLabel》 :https://www.yuque.com/bunny-6ixda/bgxtva/wtw4x4r8kbvxwgac?singleDoc#
|
||||
|
||||
## Docker安装
|
||||
|
||||
- docker镜像:
|
||||
|
||||
- https://hub.docker.com/r/gitlab/gitlab-ee/tags?name=17.9.6
|
||||
- https://hub.docker.com/r/gitlab/gitlab-runner/tags?name=17.11.0
|
||||
|
||||
```bash
|
||||
sudo apt-get remove docker docker-engine docker.io containerd runc
|
||||
sudo apt update
|
||||
sudo apt upgrade
|
||||
sudo apt-get install ca-certificates curl gnupg lsb-release
|
||||
# 添加Docker官方GPG密钥
|
||||
sudo curl -fsSL http://mirrors.aliyun.com/docker-ce/linux/ubuntu/gpg | sudo apt-key add -
|
||||
sudo add-apt-repository "deb [arch=amd64] http://mirrors.aliyun.com/docker-ce/linux/ubuntu $(lsb_release -cs) stable"
|
||||
# 安装docker
|
||||
sudo apt-get install docker-ce docker-ce-cli containerd.io
|
||||
# 默认情况下,只有root用户和docker组的用户才能运行Docker命令。我们可以将当前用户添加到docker组,以避免每次使用Docker时都需要使用sudo,设置完成后退出当前用户之后再进入既可
|
||||
sudo usermod -aG docker $USER
|
||||
# 运行docker
|
||||
sudo systemctl start docker
|
||||
# 安装工具
|
||||
sudo apt-get -y install apt-transport-https ca-certificates curl software-properties-common
|
||||
# 重启docker
|
||||
sudo service docker restart
|
||||
# 重启终端生效
|
||||
exit
|
||||
```
|
||||
|
||||
配置镜像源
|
||||
|
||||
```bash
|
||||
# 创建目录
|
||||
sudo mkdir -p /etc/docker
|
||||
# 写入配置文件
|
||||
sudo tee /etc/docker/daemon.json <<-'EOF'
|
||||
{
|
||||
"registry-mirrors": [
|
||||
"https://docker-0.unsee.tech",
|
||||
"https://docker-cf.registry.cyou",
|
||||
"https://docker.1panel.live"
|
||||
]
|
||||
}
|
||||
EOF
|
||||
|
||||
# 重启docker服务
|
||||
sudo systemctl daemon-reload && sudo systemctl restart docker
|
||||
```
|
||||
|
||||
## 环境搭建
|
||||
|
||||
### 安装JDK21
|
||||
|
||||
```bash
|
||||
# 安装JDK21
|
||||
wget https://download.oracle.com/java/21/latest/jdk-21_linux-x64_bin.deb
|
||||
sudo dpkg -i jdk-21_linux-x64_bin.deb
|
||||
java --version
|
||||
```
|
||||
|
||||
### Maven 3.8.8安装
|
||||
|
||||
#### 安装
|
||||
|
||||
```bash
|
||||
# 安装maven
|
||||
wget https://archive.apache.org/dist/maven/maven-3/3.8.8/binaries/apache-maven-3.8.8-bin.tar.gz
|
||||
sudo mkdir -p /opt/maven
|
||||
sudo tar -xzf apache-maven-3.8.8-bin.tar.gz -C /opt/maven
|
||||
sudo mv /opt/maven/apache-maven-3.8.8 /opt/maven/maven-3.8.8
|
||||
|
||||
# 修改镜像配置
|
||||
cd /opt/maven/maven-3.8.8/conf
|
||||
# 赋予权限修改
|
||||
sudo chmod 666 settings.xml
|
||||
|
||||
# 编写配置
|
||||
sudo vim /etc/profile
|
||||
|
||||
# 添加以下内容
|
||||
# export PATH=$PATH:/opt/maven/maven-3.8.8/bin
|
||||
|
||||
# 刷新配置
|
||||
source /etc/profile
|
||||
mvn -V
|
||||
```
|
||||
|
||||
#### maven的镜像
|
||||
|
||||
```xml
|
||||
<mirror>
|
||||
<id>aliyun</id>
|
||||
<name>Aliyun Maven Mirror</name>
|
||||
<url>https://maven.aliyun.com/repository/public</url>
|
||||
<mirrorOf>central</mirrorOf>
|
||||
</mirror>
|
||||
```
|
||||
|
||||
## 安装Gitlab
|
||||
|
||||
```bash
|
||||
# Ubuntu
|
||||
wget https://mirrors.tuna.tsinghua.edu.cn/gitlab-ce/ubuntu/pool/focal/main/g/gitlab-ce/gitlab-ce_18.0.0-ce.0_amd64.deb
|
||||
|
||||
# dpkg
|
||||
sudo dpkg -i gitlab-ce_18.0.0-ce.0_amd64.deb
|
||||
```
|
||||
|
||||
### 编辑配置
|
||||
|
||||
```bash
|
||||
# 编辑站点
|
||||
sudo vim /etc/gitlab/gitlab.rb
|
||||
```
|
||||
|
||||
修改下面内容
|
||||
|
||||
```bash
|
||||
external_url 'http://192.168.95.134:3001'
|
||||
```
|
||||
|
||||
应用配置
|
||||
|
||||
```bash
|
||||
# 应用配置
|
||||
sudo gitlab-ctl reconfigure
|
||||
```
|
||||
|
||||
### 常用命令
|
||||
|
||||
```bash
|
||||
# 服务控制
|
||||
sudo gitlab-ctl start
|
||||
sudo gitlab-ctl status
|
||||
sudo gitlab-ctl stop
|
||||
|
||||
# 应用配置
|
||||
sudo gitlab-ctl reconfigure
|
||||
|
||||
# 重启
|
||||
sudo gitlab-ctl restart
|
||||
```
|
||||
|
||||
### 查看密码
|
||||
|
||||
```bash
|
||||
# 24 小时后自动删除
|
||||
sudo cat /etc/gitlab/initial_root_password
|
||||
```
|
||||
|
||||
## 安装Gitlab-Runner
|
||||
|
||||
```bash
|
||||
# 需要 gitlab-runner-helper-images
|
||||
wget https://mirrors.tuna.tsinghua.edu.cn/gitlab-runner/ubuntu/pool/focal/main/g/gitlab-runner-helper-images/gitlab-runner-helper-images_18.0.1-1_all.deb
|
||||
sudo dpkg -i gitlab-runner-helper-images_18.0.1-1_all.deb
|
||||
|
||||
# 之后安装 gitlab-runner
|
||||
wget https://mirrors.tuna.tsinghua.edu.cn/gitlab-runner/ubuntu/pool/focal/main/g/gitlab-runner/gitlab-runner_18.0.1-1_amd64.deb
|
||||
sudo dpkg -i gitlab-runner_18.0.1-1_amd64.deb
|
||||
```
|
||||
|
||||
### 先下载后安装
|
||||
|
||||
```bash
|
||||
# Ubuntu
|
||||
wget https://mirrors.tuna.tsinghua.edu.cn/gitlab-runner/ubuntu/pool/focal/main/g/gitlab-runner-helper-images/gitlab-runner-helper-images_18.0.1-1_all.deb
|
||||
wget https://mirrors.tuna.tsinghua.edu.cn/gitlab-runner/ubuntu/pool/focal/main/g/gitlab-runner/gitlab-runner_18.0.1-1_amd64.deb
|
||||
|
||||
# dpkg
|
||||
sudo dpkg -i gitlab-runner-helper-images_18.0.1-1_all.deb
|
||||
sudo dpkg -i gitlab-runner_18.0.1-1_amd64.deb
|
||||
```
|
||||
|
||||
### 配置Gitlab-Runner用户
|
||||
|
||||
> [!NOTE]
|
||||
>
|
||||
> 如果有需要清理缓存:`sudo rm -rf /opt/maven/maven-3.8.8/conf/builds/**`**
|
||||
>
|
||||
> gitlab-ce:https://mirrors.tuna.tsinghua.edu.cn/gitlab-ce/ubuntu/pool/focal/main/g/gitlab-ce/
|
||||
>
|
||||
> gitlab-runner-helper-images:https://mirrors.tuna.tsinghua.edu.cn/gitlab-runner/ubuntu/pool/focal/main/g/gitlab-runner-helper-images/
|
||||
>
|
||||
> gitlab-runner:https://mirrors.tuna.tsinghua.edu.cn/gitlab-runner/ubuntu/pool/focal/main/g/gitlab-runner/
|
||||
|
||||
```bash
|
||||
sudo gitlab-runner uninstall
|
||||
sudo gitlab-runner install --working-directory /home/gitlab-runner --user root
|
||||
sudo systemctl restart gitlab-runner
|
||||
```
|
||||
|
||||
### 检查 GitLab Runner 配置
|
||||
|
||||
```bash
|
||||
sudo vim /etc/gitlab-runner/config.toml
|
||||
```
|
||||
|
||||
修改文件
|
||||
|
||||
```bash
|
||||
[[runners]]
|
||||
name = "my-runner"
|
||||
executor = "shell"
|
||||
shell = "bash"
|
||||
user = "gitlab-runner" # 确保用户有权限
|
||||
working_directory = "/home/gitlab-runner"
|
||||
```
|
||||
|
||||
### 检查 Maven 安装目录权限
|
||||
|
||||
```bash
|
||||
sudo chmod 777 -R /opt/maven/maven-3.8.8
|
||||
sudo chmod 777 -R /opt/maven/maven-3.8.8/
|
||||
sudo chown -R gitlab-runner:gitlab-runner /opt/maven/maven-3.8.8/
|
||||
```
|
||||
|
||||
## CI/CD脚本示例
|
||||
|
||||
如果构建出现`pending`情况大部分情况下,是文件写错了,要么是`Gitlab-Runner`标签没写对
|
||||
|
||||
```yml
|
||||
stages:
|
||||
- build
|
||||
- build-docker
|
||||
- deploy
|
||||
|
||||
variables:
|
||||
CONTAINER_NAME: "bunny-auth-server"
|
||||
DOCKER_TAG: "4.0.0"
|
||||
|
||||
build-job:
|
||||
stage: build
|
||||
script:
|
||||
- echo "Compiling the code..."
|
||||
- mvn clean package -DskipTests
|
||||
- echo "Compile complete."
|
||||
- docker pull openjdk:24-ea-17-jdk-oraclelinux9
|
||||
- echo "docker pull complete."
|
||||
- docker build -f Dockerfile -t $CONTAINER_NAME:$DOCKER_TAG .
|
||||
- echo "Application successfully deployed."
|
||||
|
||||
deploy-job:
|
||||
stage: deploy
|
||||
environment: production
|
||||
script:
|
||||
- echo "Deploying application..."
|
||||
- docker stop $CONTAINER_NAME || true
|
||||
- docker rm $CONTAINER_NAME || true
|
||||
- docker run -d -p 7070:7070 -p 8000:8000 --name $CONTAINER_NAME --restart always $CONTAINER_NAME:$DOCKER_TAG
|
||||
- echo "Application successfully deployed."
|
||||
```
|
|
@ -0,0 +1,3 @@
|
|||
# Drools
|
||||
|
||||
包含Gitlab部署
|
|
@ -0,0 +1,126 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-parent</artifactId>
|
||||
<version>3.4.4</version>
|
||||
<relativePath/> <!-- lookup parent from repository -->
|
||||
</parent>
|
||||
<groupId>cn.bunny</groupId>
|
||||
<artifactId>drools</artifactId>
|
||||
<version>0.0.1-SNAPSHOT</version>
|
||||
<name>drools</name>
|
||||
<description>drools</description>
|
||||
<url/>
|
||||
<licenses>
|
||||
<license/>
|
||||
</licenses>
|
||||
<developers>
|
||||
<developer/>
|
||||
</developers>
|
||||
|
||||
<properties>
|
||||
<java.version>17</java.version>
|
||||
<drools.version>10.0.0</drools.version>
|
||||
</properties>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-thymeleaf</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-devtools</artifactId>
|
||||
<scope>runtime</scope>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.drools</groupId>
|
||||
<artifactId>drools-ruleunits-engine</artifactId>
|
||||
<version>${drools.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.drools</groupId>
|
||||
<artifactId>drools-model-compiler</artifactId>
|
||||
<version>${drools.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.drools</groupId>
|
||||
<artifactId>drools-core</artifactId>
|
||||
<version>${drools.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.drools</groupId>
|
||||
<artifactId>drools-compiler</artifactId>
|
||||
<version>${drools.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.drools</groupId>
|
||||
<artifactId>drools-decisiontables</artifactId>
|
||||
<version>${drools.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.drools</groupId>
|
||||
<artifactId>drools-xml-support</artifactId>
|
||||
<version>${drools.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.drools</groupId>
|
||||
<artifactId>drools-mvel</artifactId>
|
||||
<version>${drools.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<configuration>
|
||||
<annotationProcessorPaths>
|
||||
<path>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
</path>
|
||||
</annotationProcessorPaths>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
<configuration>
|
||||
<excludes>
|
||||
<exclude>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
</exclude>
|
||||
</excludes>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.kie</groupId>
|
||||
<artifactId>kie-maven-plugin</artifactId>
|
||||
<version>7.73.0.Final</version>
|
||||
<extensions>true</extensions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
</project>
|
|
@ -0,0 +1,5 @@
|
|||
git checkout master
|
||||
git merge dev
|
||||
git push --all
|
||||
git push --tags
|
||||
git checkout dev
|
|
@ -0,0 +1,13 @@
|
|||
package cn.bunny.drools;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
|
||||
@SpringBootApplication
|
||||
public class DroolsApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(DroolsApplication.class, args);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
package cn.bunny.drools.bean.demo;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class BookDisCount {
|
||||
|
||||
/* 订单原始价格,即优惠前价格 */
|
||||
private Double originalPrice;
|
||||
|
||||
/* 订单真实价格,即优惠后价格 */
|
||||
private Double realPrice;
|
||||
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
package cn.bunny.drools.bean.demo.demo1;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class Order {
|
||||
|
||||
/* 金额 */
|
||||
private BigDecimal amount;
|
||||
|
||||
/* 最终金额 */
|
||||
private BigDecimal finalAmount;
|
||||
|
||||
/* 是否是vip */
|
||||
private Boolean vip;
|
||||
|
||||
/* 积分 */
|
||||
private double score;
|
||||
|
||||
/* 折扣 */
|
||||
private double discount;
|
||||
|
||||
public void applyDiscount(double additionalDiscount) {
|
||||
this.discount += additionalDiscount;
|
||||
this.finalAmount = this.amount.multiply(new BigDecimal(1 - this.discount))
|
||||
.setScale(2, RoundingMode.HALF_UP);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
package cn.bunny.drools.bean.demo.demo2;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class AccessRequest {
|
||||
|
||||
/* 用户 */
|
||||
private User user;
|
||||
|
||||
/* 访问的内容 */
|
||||
private Content content;
|
||||
|
||||
/* 有权访问 */
|
||||
private boolean granted;
|
||||
|
||||
/* 拒绝的理由 */
|
||||
private String denialReason;
|
||||
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
package cn.bunny.drools.bean.demo.demo2;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class Content {
|
||||
|
||||
private String id;
|
||||
|
||||
/* 内容类型 */
|
||||
private ContentType type;
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
package cn.bunny.drools.bean.demo.demo2;
|
||||
|
||||
/* 类容类型 */
|
||||
public enum ContentType {
|
||||
GENERAL, // 普通内容
|
||||
R_RATED, // R级内容
|
||||
ALCOHOL // 酒精类产品
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
package cn.bunny.drools.bean.demo.demo2;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class User {
|
||||
|
||||
/* 用户名 */
|
||||
private String username;
|
||||
|
||||
/* 年龄 */
|
||||
private Integer age;
|
||||
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
package cn.bunny.drools.bean.demo.demo3;
|
||||
|
||||
public interface ApprovalDecision {
|
||||
|
||||
/* 最高额度 */
|
||||
String APPROVED_HIGH = "APPROVED_HIGH";
|
||||
|
||||
/* 中等额度 */
|
||||
String APPROVED_MEDIUM = "APPROVED_MEDIUM";
|
||||
|
||||
/* 最低额度 */
|
||||
String APPROVED_LOW = "APPROVED_LOW";
|
||||
|
||||
/* 拒绝 */
|
||||
String REJECTED = "REJECTED";
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
package cn.bunny.drools.bean.demo.demo3;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class CreditCardApplication {
|
||||
|
||||
private String applicantId;
|
||||
|
||||
private String applicantName;
|
||||
|
||||
/* 信用评分(300-850) */
|
||||
private int creditScore;
|
||||
|
||||
/* 年收入(美元) */
|
||||
private double annualIncome;
|
||||
|
||||
/* 现有负债(美元) */
|
||||
private double existingDebt;
|
||||
|
||||
/* 审批结果(APPROVED_HIGH, APPROVED_MEDIUM, APPROVED_LOW, REJECTED) */
|
||||
private String decision;
|
||||
|
||||
/* 批准额度 */
|
||||
private double approvedLimit;
|
||||
|
||||
public boolean percentage() {
|
||||
return existingDebt / annualIncome > 0.5;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
package cn.bunny.drools.bean.exercise;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class ComparisonOperation {
|
||||
|
||||
/* 字段名称 */
|
||||
private String field;
|
||||
|
||||
/* 操作列表 */
|
||||
private List<String> operatorList;
|
||||
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
package cn.bunny.drools.config;
|
||||
|
||||
import org.kie.api.KieServices;
|
||||
import org.kie.api.builder.KieBuilder;
|
||||
import org.kie.api.builder.KieFileSystem;
|
||||
import org.kie.api.builder.KieModule;
|
||||
import org.kie.api.runtime.KieContainer;
|
||||
import org.kie.api.runtime.KieSession;
|
||||
import org.kie.internal.io.ResourceFactory;
|
||||
|
||||
public class DroolsConfiguration {
|
||||
|
||||
/**
|
||||
* 读取单个文件内容
|
||||
*
|
||||
* @param filename 规则文件名.后缀名
|
||||
* @return KieContainer
|
||||
*/
|
||||
public static KieSession createKieSession(String filename) {
|
||||
KieServices kieServices = KieServices.Factory.get();
|
||||
KieFileSystem kfs = kieServices.newKieFileSystem();
|
||||
kfs.write(ResourceFactory.newClassPathResource("rules/" + filename));
|
||||
|
||||
KieBuilder kieBuilder = kieServices.newKieBuilder(kfs).buildAll();
|
||||
KieModule kieModule = kieBuilder.getKieModule();
|
||||
KieContainer container = kieServices.newKieContainer(kieModule.getReleaseId());
|
||||
|
||||
return container.newKieSession();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
<kmodule xmlns="http://www.drools.org/xsd/kmodule">
|
||||
|
||||
<!-- 练习 -->
|
||||
<kbase name="Exercise" packages="rules.exercise" default="false">
|
||||
|
||||
</kbase>
|
||||
|
||||
<!-- demo 练习 -->
|
||||
<kbase name="BookDisCount" packages="rules.demo" default="true">
|
||||
|
||||
</kbase>
|
||||
|
||||
</kmodule>
|
|
@ -0,0 +1,10 @@
|
|||
spring:
|
||||
application:
|
||||
name: drools
|
||||
|
||||
logging:
|
||||
level:
|
||||
cn.bunny.service.mapper: info
|
||||
cn.bunny.service.controller: info
|
||||
cn.bunny.service.service: info
|
||||
root: info
|
|
@ -0,0 +1,10 @@
|
|||
_ _
|
||||
| |__ _ _ _ __ _ __ _ _ (_) __ ___ ____ _
|
||||
| '_ \| | | | '_ \| '_ \| | | | | |/ _` \ \ / / _` |
|
||||
| |_) | |_| | | | | | | | |_| | | | (_| |\ V | (_| |
|
||||
|_.__/ \__,_|_| |_|_| |_|\__, | _/ |\__,_| \_/ \__,_|
|
||||
|___/ |__/
|
||||
|
||||
Service Name${spring.application.name}
|
||||
SpringBoot Version: ${spring-boot.version}${spring-boot.formatted-version}
|
||||
SpringActive:${spring.profiles.active}
|
|
@ -0,0 +1,69 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<configuration>
|
||||
<contextName>logback</contextName>
|
||||
|
||||
<!-- 格式化 年-月-日 输出 -->
|
||||
<timestamp key="datetime" datePattern="yyyy-MM-dd"/>
|
||||
|
||||
<!--编码-->
|
||||
<property name="ENCODING" value="UTF-8"/>
|
||||
|
||||
<!-- 控制台日志 -->
|
||||
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<!-- 临界值过滤器 -->
|
||||
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
|
||||
<level>INFO</level>
|
||||
</filter>
|
||||
<encoder>
|
||||
<pattern>%cyan([%thread %d{yyyy-MM-dd HH:mm:ss}]) %yellow(%-5level) %green(%logger{100}).%boldRed(%method)-%boldMagenta(%line)-%blue(%msg%n)
|
||||
</pattern>
|
||||
<charset>${ENCODING}</charset>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<!-- 文件日志 -->
|
||||
<appender name="FILE" class="ch.qos.logback.core.FileAppender">
|
||||
<file>logs/${datetime}/financial-server.log</file>
|
||||
<append>true</append>
|
||||
<encoder>
|
||||
<pattern>%date{yyyy-MM-dd HH:mm:ss} [%-5level] %thread %file:%line %logger %msg%n</pattern>
|
||||
<charset>${ENCODING}</charset>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<!-- 让SpringBoot内部日志ERROR级别 减少日志输出 -->
|
||||
<logger name="org.springframework" level="ERROR" additivity="false">
|
||||
<appender-ref ref="STOUT"/>
|
||||
</logger>
|
||||
|
||||
<!-- 让mybatis整合包日志ERROR 减少日志输出 -->
|
||||
<logger name="org.mybatis" level="ERROR" additivity="false">
|
||||
<appender-ref ref="STOUT"/>
|
||||
</logger>
|
||||
|
||||
<!-- 让ibatis 日志ERROR 减少日志输出 -->
|
||||
<logger name="org.apache.ibatis" level="ERROR" additivity="false">
|
||||
<appender-ref ref="STOUT"/>
|
||||
</logger>
|
||||
|
||||
<!-- 让 tomcat包打印日志 日志ERROR 减少日志输出 -->
|
||||
<logger name="org.apache" level="ERROR" additivity="false">
|
||||
<appender-ref ref="STOUT"/>
|
||||
</logger>
|
||||
|
||||
<!-- 我们自己开发的程序为DEBUG -->
|
||||
<logger name="cn.bunny" level="DEBUG" additivity="false">
|
||||
<appender-ref ref="STOUT"/>
|
||||
</logger>
|
||||
|
||||
<logger name="com.baomidou" level="ERROR" additivity="false">
|
||||
<appender-ref ref="STOUT"/>
|
||||
</logger>
|
||||
|
||||
<!-- 根日志记录器:INFO级别 -->
|
||||
<root level="INFO">
|
||||
<appender-ref ref="CONSOLE"/>
|
||||
<appender-ref ref="FILE"/>
|
||||
</root>
|
||||
|
||||
</configuration>
|
|
@ -0,0 +1,51 @@
|
|||
package rules;
|
||||
|
||||
import cn.bunny.drools.bean.demo2.User;
|
||||
import cn.bunny.drools.bean.demo2.AccessRequest;
|
||||
import cn.bunny.drools.bean.demo2.Content;
|
||||
import cn.bunny.drools.bean.demo2.ContentType;
|
||||
|
||||
/**
|
||||
场景:年龄限制内容访问控制
|
||||
|
||||
要求:
|
||||
18岁以下用户不能访问R级内容
|
||||
21岁以下用户不能购买酒精类产品
|
||||
需要处理用户年龄和访问内容类型两个维度
|
||||
*/
|
||||
|
||||
rule "Deny R-rated content for under 18"
|
||||
when
|
||||
$request: AccessRequest(
|
||||
user.age < 18,
|
||||
content.type == ContentType.R_RATED,
|
||||
granted == true
|
||||
)
|
||||
then
|
||||
$request.setGranted(false);
|
||||
$request.setDenialReason("未满18岁禁止访问R级内容");
|
||||
System.out.println("拒绝R级内容访问: 用户年龄 " + $request.getUser().getAge());
|
||||
end
|
||||
|
||||
rule "Deny alcohol purchase for under 21"
|
||||
when
|
||||
$request: AccessRequest(
|
||||
user.age < 21,
|
||||
content.type == ContentType.ALCOHOL,
|
||||
granted == true
|
||||
)
|
||||
then
|
||||
$request.setGranted(false);
|
||||
$request.setDenialReason("未满21岁禁止购买酒精类产品");
|
||||
System.out.println("拒绝酒精购买: 用户年龄 " + $request.getUser().getAge());
|
||||
end
|
||||
|
||||
// 规则3: 默认允许访问
|
||||
rule "Default access grant"
|
||||
when
|
||||
$request: AccessRequest(granted == false)
|
||||
then
|
||||
// 如果没有被前面的规则拒绝,则允许访问
|
||||
$request.setGranted(true);
|
||||
System.out.println("允许访问: " + $request.getContent().getType());
|
||||
end
|
|
@ -0,0 +1,40 @@
|
|||
package rules;
|
||||
import cn.bunny.drools.bean.demo.BookDisCount
|
||||
|
||||
/*
|
||||
入门示例
|
||||
*/
|
||||
rule "There is no discount for books purchased with a total price below 100 yuan"
|
||||
when
|
||||
$bookDisCount:BookDisCount(originalPrice < 100)
|
||||
then
|
||||
$bookDisCount.setRealPrice($bookDisCount.getOriginalPrice());
|
||||
System.out.println("小于100没有优惠");
|
||||
end
|
||||
|
||||
rule "20 yuan off the total price of 100 to 200 yuan"
|
||||
when
|
||||
$bookDisCount:BookDisCount(originalPrice > 100 && originalPrice < 200 )
|
||||
then
|
||||
Double originalPrice = $bookDisCount.getOriginalPrice();
|
||||
$bookDisCount.setRealPrice(originalPrice - 20);
|
||||
System.out.println("所购图书总价在100到200元的优惠20元");
|
||||
end
|
||||
|
||||
rule "50 yuan off the total book price of 200 to 300 yuan"
|
||||
when
|
||||
$bookDisCount:BookDisCount(originalPrice > 200 && originalPrice < 300)
|
||||
then
|
||||
Double originalPrice = $bookDisCount.getOriginalPrice();
|
||||
$bookDisCount.setRealPrice(originalPrice - 50);
|
||||
System.out.println("所购图书总价在200到300元的优惠50元");
|
||||
end
|
||||
|
||||
rule "A discount of 100 yuan will be given if the total price is over 300 yuan"
|
||||
when
|
||||
$bookDisCount:BookDisCount(originalPrice > 300)
|
||||
then
|
||||
Double originalPrice = $bookDisCount.getOriginalPrice();
|
||||
$bookDisCount.setRealPrice(originalPrice - 100);
|
||||
System.out.println("所购图书总价在300元以上的优惠100元");
|
||||
end
|
|
@ -0,0 +1,89 @@
|
|||
package rules;
|
||||
|
||||
import cn.bunny.drools.bean.demo3.ApprovalDecision
|
||||
import cn.bunny.drools.bean.demo3.CreditCardApplication
|
||||
import cn.bunny.drools.bean.demo.demo3.ApprovalDecision;
|
||||
|
||||
/**
|
||||
根据申请人的信用评分、收入和现有负债自动决策
|
||||
信用评分>700且收入>5万:批准高额度
|
||||
信用评分600-700:批准中等额度
|
||||
信用评分<600但收入>10万:批准低额度
|
||||
其他情况拒绝
|
||||
*/
|
||||
|
||||
// 规则1: 高额度批准
|
||||
rule "Approve High Limit - Excellent Credit"
|
||||
salience 9 // 提高优先级
|
||||
when
|
||||
$application: CreditCardApplication(
|
||||
creditScore > 700,
|
||||
annualIncome > 50000,
|
||||
decision == null,
|
||||
existingDebt / annualIncome <= 0.5
|
||||
)
|
||||
then
|
||||
$application.setDecision(ApprovalDecision.APPROVED_HIGH);
|
||||
$application.setApprovedLimit(20000);
|
||||
System.out.println("批准高额度信用卡: " + $application.getApplicantName());
|
||||
drools.halt();
|
||||
end
|
||||
|
||||
// 规则2: 中等额度批准
|
||||
rule "Approve Medium Limit - Good Credit"
|
||||
salience 8
|
||||
when
|
||||
$application: CreditCardApplication(
|
||||
creditScore >= 600 && creditScore <= 700,
|
||||
decision == null,
|
||||
existingDebt / annualIncome <= 0.5
|
||||
)
|
||||
then
|
||||
$application.setDecision(ApprovalDecision.APPROVED_MEDIUM);
|
||||
$application.setApprovedLimit(10000);
|
||||
System.out.println("批准中等额度信用卡: " + $application.getApplicantName());
|
||||
drools.halt();
|
||||
end
|
||||
|
||||
// 规则3: 低额度批准
|
||||
rule "Approve Low Limit - High Income with Low Credit"
|
||||
salience 6
|
||||
when
|
||||
$application: CreditCardApplication(
|
||||
creditScore < 600,
|
||||
annualIncome > 100000,
|
||||
decision == null,
|
||||
existingDebt / annualIncome <= 0.5
|
||||
)
|
||||
then
|
||||
$application.setDecision(ApprovalDecision.APPROVED_LOW);
|
||||
$application.setApprovedLimit(5000);
|
||||
System.out.println("批准低额度信用卡(高收入特殊情况): " + $application.getApplicantName());
|
||||
drools.halt();
|
||||
end
|
||||
|
||||
// 规则4: 负债过高拒绝
|
||||
rule "Reject Application - High Debt Ratio"
|
||||
salience 10 // 最高优先级
|
||||
when
|
||||
$application: CreditCardApplication(
|
||||
existingDebt / annualIncome > 0.5,
|
||||
decision == null
|
||||
)
|
||||
then
|
||||
$application.setDecision(ApprovalDecision.REJECTED);
|
||||
$application.setApprovedLimit(0);
|
||||
System.out.println("拒绝申请: 负债过高 - " + $application.getApplicantName());
|
||||
drools.halt();
|
||||
end
|
||||
|
||||
// 规则5: 默认拒绝
|
||||
rule "Default Rejection"
|
||||
salience 1 // 最低优先级
|
||||
when
|
||||
$application: CreditCardApplication(decision == null)
|
||||
then
|
||||
$application.setDecision(ApprovalDecision.REJECTED);
|
||||
$application.setApprovedLimit(0);
|
||||
System.out.println("拒绝申请: 不满足任何批准条件 - " + $application.getApplicantName());
|
||||
end
|
|
@ -0,0 +1,34 @@
|
|||
package rules;
|
||||
|
||||
import cn.bunny.drools.bean.demo1.Order;
|
||||
|
||||
/**
|
||||
* 当订单金额超过100元时,给予5%折扣
|
||||
* VIP客户额外获得2%折扣
|
||||
* 两种折扣可以叠加
|
||||
*/
|
||||
// 大于100元 5折优惠
|
||||
rule "Give 5% discount for orders over 100"
|
||||
when
|
||||
$order: Order(amount > 100)
|
||||
then
|
||||
$order.applyDiscount(0.05);
|
||||
System.out.println("当订单金额超过100元时,给予5%折扣");
|
||||
end
|
||||
|
||||
// VIP客户额外获得2%折扣
|
||||
rule "Give additional 2% discount for VIP customers"
|
||||
when
|
||||
$order: Order(amount > 100,vip == true)
|
||||
then
|
||||
$order.applyDiscount(0.02);
|
||||
System.out.println("是个Vip");
|
||||
end
|
||||
|
||||
// 没有优惠
|
||||
rule "No discount for small orders"
|
||||
when
|
||||
$order: Order(amount <= 100)
|
||||
then
|
||||
System.out.println("没有优惠");
|
||||
end
|
|
@ -0,0 +1,20 @@
|
|||
package rules.exercise;
|
||||
|
||||
import cn.bunny.drools.bean.exercise.ComparisonOperation;
|
||||
|
||||
/*
|
||||
使用 contains 匹配
|
||||
*/
|
||||
rule "exercise-01-contains"
|
||||
when
|
||||
$comparisonOperation:ComparisonOperation(field contains "张三名称")
|
||||
then
|
||||
System.out.println("01 field 匹配到");
|
||||
end
|
||||
|
||||
rule "exercise list"
|
||||
when
|
||||
$comparisonOperation:ComparisonOperation(operatorList contains field)
|
||||
then
|
||||
System.out.println("01 operatorList 匹配到");
|
||||
end
|
|
@ -0,0 +1,16 @@
|
|||
package rules.exercise;
|
||||
import cn.bunny.drools.bean.exercise.ComparisonOperation;
|
||||
|
||||
rule "exercise-02-contains"
|
||||
when
|
||||
$comparisonOperation:ComparisonOperation(field not contains "zxc")
|
||||
then
|
||||
System.out.println("02 field 匹配到");
|
||||
end
|
||||
|
||||
rule "exercise list 02"
|
||||
when
|
||||
$comparisonOperation:ComparisonOperation(operatorList not contains field)
|
||||
then
|
||||
System.out.println("02 operatorList 匹配到");
|
||||
end
|
|
@ -0,0 +1,23 @@
|
|||
package rules.exercise;
|
||||
import cn.bunny.drools.bean.exercise.ComparisonOperation;
|
||||
|
||||
rule "exercise-03-contains"
|
||||
when
|
||||
$comparisonOperation:ComparisonOperation(field memberOf "qwe")
|
||||
then
|
||||
System.out.println("03 field 匹配到");
|
||||
end
|
||||
|
||||
rule "exercise list 03 memberOf"
|
||||
when
|
||||
$comparisonOperation:ComparisonOperation("sss" memberOf operatorList)
|
||||
then
|
||||
System.out.println("03 operatorList memberOf 匹配到");
|
||||
end
|
||||
|
||||
rule "exercise list 03"
|
||||
when
|
||||
$comparisonOperation:ComparisonOperation(operatorList not memberOf field)
|
||||
then
|
||||
System.out.println("03 not memberOf 匹配到");
|
||||
end
|
|
@ -0,0 +1,23 @@
|
|||
package rules.exercise;
|
||||
import cn.bunny.drools.bean.exercise.ComparisonOperation;
|
||||
|
||||
rule "exercise-04-contains"
|
||||
activation-group "group_1"
|
||||
enabled false
|
||||
// 方言
|
||||
// dialect "mvel"
|
||||
when
|
||||
// 让其返回 ture 表示匹配成功
|
||||
// eval(true)
|
||||
$comparisonOperation:ComparisonOperation(field matches "q.*")
|
||||
then
|
||||
System.out.println("04 field matches q.* 匹配到");
|
||||
end
|
||||
|
||||
rule "exercise-04-contains-1"
|
||||
activation-group "group_1"
|
||||
when
|
||||
$comparisonOperation:ComparisonOperation(field not matches "q.*")
|
||||
then
|
||||
System.out.println("04 field not matches q.* 匹配到");
|
||||
end
|
|
@ -0,0 +1,29 @@
|
|||
package cn.bunny.drools.demo;
|
||||
|
||||
import cn.bunny.drools.bean.demo.BookDisCount;
|
||||
import org.kie.api.KieServices;
|
||||
import org.kie.api.runtime.KieContainer;
|
||||
import org.kie.api.runtime.KieSession;
|
||||
|
||||
public class _00BookDisCountTest {
|
||||
public static void main(String[] args) {
|
||||
KieServices kieServices = KieServices.Factory.get();
|
||||
KieContainer container = kieServices.newKieClasspathContainer();
|
||||
KieSession kieSession = container.newKieSession();
|
||||
|
||||
BookDisCount order = new BookDisCount();
|
||||
order.setOriginalPrice(199.0);
|
||||
|
||||
// 插入 Fact
|
||||
kieSession.insert(order);
|
||||
|
||||
// 激活规则
|
||||
kieSession.fireAllRules();
|
||||
|
||||
// 关闭会话
|
||||
kieSession.dispose();
|
||||
|
||||
System.out.println(order);
|
||||
kieSession.close();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
package cn.bunny.drools.demo;
|
||||
|
||||
import cn.bunny.drools.bean.demo.demo1.Order;
|
||||
import cn.bunny.drools.config.DroolsConfiguration;
|
||||
import org.kie.api.runtime.KieSession;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
class _01OrderTest {
|
||||
public static void main(String[] args) {
|
||||
try (KieSession kieSession = DroolsConfiguration.createKieSession("order.drl")) {
|
||||
|
||||
Order order = new Order();
|
||||
order.setAmount(new BigDecimal(99));
|
||||
order.setAmount(new BigDecimal(101));
|
||||
// order.setVip(true);
|
||||
|
||||
// 插入order
|
||||
kieSession.insert(order);
|
||||
kieSession.fireAllRules();
|
||||
kieSession.dispose();
|
||||
|
||||
System.out.println(order);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,61 @@
|
|||
package cn.bunny.drools.demo;
|
||||
|
||||
import cn.bunny.drools.bean.demo.demo2.AccessRequest;
|
||||
import cn.bunny.drools.bean.demo.demo2.Content;
|
||||
import cn.bunny.drools.bean.demo.demo2.ContentType;
|
||||
import cn.bunny.drools.bean.demo.demo2.User;
|
||||
import cn.bunny.drools.config.DroolsConfiguration;
|
||||
import org.kie.api.runtime.KieSession;
|
||||
|
||||
public class _02AgeVerifyTest {
|
||||
private static AccessRequest verifyAccess(User adult, Content rRatedMovie) {
|
||||
// 测试1: 成人可以访问所有内容
|
||||
AccessRequest request = AccessRequest.builder().user(adult).content(rRatedMovie).build();
|
||||
// 默认允许
|
||||
request.setGranted(true);
|
||||
|
||||
try (KieSession kieSession = DroolsConfiguration.createKieSession("age-verify.drl")) {
|
||||
|
||||
kieSession.insert(request);
|
||||
kieSession.insert(adult);
|
||||
kieSession.insert(rRatedMovie);
|
||||
|
||||
kieSession.fireAllRules();
|
||||
kieSession.dispose();
|
||||
}
|
||||
|
||||
return request;
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
User adult = new User("成人用户", 25);
|
||||
User teen = new User("青少年", 17);
|
||||
User youngAdult = new User("年轻成人", 20);
|
||||
|
||||
Content rRatedMovie = new Content("R级电影", ContentType.R_RATED);
|
||||
Content alcohol = new Content("啤酒", ContentType.ALCOHOL);
|
||||
Content general = new Content("普通内容", ContentType.GENERAL);
|
||||
|
||||
// 测试1: 成人可以访问所有内容
|
||||
AccessRequest request1 = verifyAccess(adult, alcohol);
|
||||
System.out.println(request1.isGranted());
|
||||
|
||||
AccessRequest request2 = verifyAccess(adult, rRatedMovie);
|
||||
System.out.println(request2.isGranted());
|
||||
|
||||
// 测试2: 青少年不能访问R级内容
|
||||
AccessRequest request3 = verifyAccess(teen, rRatedMovie);
|
||||
System.out.println(request3.isGranted());
|
||||
System.out.println(request3.getDenialReason());
|
||||
|
||||
// 测试3: 20岁用户不能购买酒精
|
||||
AccessRequest request4 = verifyAccess(youngAdult, alcohol);
|
||||
System.out.println(request4.isGranted());
|
||||
System.out.println(request4.getDenialReason());
|
||||
|
||||
// 测试4: 所有用户都可以访问普通内容
|
||||
AccessRequest request5 = verifyAccess(teen, general);
|
||||
System.out.println(request5.isGranted());
|
||||
System.out.println(request5.getDenialReason());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,72 @@
|
|||
package cn.bunny.drools.demo;
|
||||
|
||||
import cn.bunny.drools.bean.demo.demo3.CreditCardApplication;
|
||||
import cn.bunny.drools.config.DroolsConfiguration;
|
||||
import org.kie.api.runtime.KieSession;
|
||||
|
||||
public class _03CreditCardApplicationTest {
|
||||
public static void main(String[] args) {
|
||||
// 测试1: 高额度批准
|
||||
CreditCardApplication app1 = CreditCardApplication.builder()
|
||||
.applicantId("001")
|
||||
.applicantName("张三")
|
||||
.creditScore(750)
|
||||
.annualIncome(60000) // 修正为符合条件
|
||||
.existingDebt(5000)
|
||||
.build();
|
||||
app1 = processApplication(app1);
|
||||
System.out.println("结果1: " + app1.getDecision() + ", 额度: " + app1.getApprovedLimit());
|
||||
|
||||
// 测试2: 中等额度批准
|
||||
CreditCardApplication app2 = CreditCardApplication.builder()
|
||||
.applicantId("002")
|
||||
.applicantName("李四")
|
||||
.creditScore(650)
|
||||
.annualIncome(45000)
|
||||
.existingDebt(10000)
|
||||
.build();
|
||||
app2 = processApplication(app2);
|
||||
System.out.println("结果2: " + app2.getDecision() + ", 额度: " + app2.getApprovedLimit());
|
||||
|
||||
// 测试3: 低额度批准(高收入但低信用分)
|
||||
CreditCardApplication app3 = CreditCardApplication.builder()
|
||||
.applicantId("003")
|
||||
.applicantName("王五")
|
||||
.creditScore(550)
|
||||
.annualIncome(120000)
|
||||
.existingDebt(20000)
|
||||
.build();
|
||||
app3 = processApplication(app3);
|
||||
System.out.println("结果3: " + app3.getDecision() + ", 额度: " + app3.getApprovedLimit());
|
||||
|
||||
// 测试4: 负债过高拒绝
|
||||
CreditCardApplication app4 = CreditCardApplication.builder()
|
||||
.applicantId("004")
|
||||
.applicantName("赵六")
|
||||
.creditScore(720)
|
||||
.annualIncome(50000)
|
||||
.existingDebt(30000)
|
||||
.build();
|
||||
app4 = processApplication(app4);
|
||||
System.out.println("结果4: " + app4.getDecision() + ", 额度: " + app4.getApprovedLimit());
|
||||
|
||||
// 测试5: 默认拒绝
|
||||
CreditCardApplication app5 = CreditCardApplication.builder()
|
||||
.applicantId("005")
|
||||
.applicantName("钱七")
|
||||
.creditScore(580)
|
||||
.annualIncome(40000)
|
||||
.existingDebt(5000)
|
||||
.build();
|
||||
app5 = processApplication(app5);
|
||||
System.out.println("结果5: " + app5.getDecision() + ", 额度: " + app5.getApprovedLimit());
|
||||
}
|
||||
|
||||
public static CreditCardApplication processApplication(CreditCardApplication application) {
|
||||
try (KieSession kieSession = DroolsConfiguration.createKieSession("credit-card-approval.drl")) {
|
||||
kieSession.insert(application);
|
||||
kieSession.fireAllRules();
|
||||
return application;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,148 @@
|
|||
package cn.bunny.drools.exercise;
|
||||
|
||||
import cn.bunny.drools.bean.exercise.ComparisonOperation;
|
||||
import org.drools.core.base.RuleNameEndsWithAgendaFilter;
|
||||
import org.drools.core.base.RuleNameMatchesAgendaFilter;
|
||||
import org.drools.core.base.RuleNameStartsWithAgendaFilter;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.kie.api.KieBase;
|
||||
import org.kie.api.KieServices;
|
||||
import org.kie.api.runtime.KieContainer;
|
||||
import org.kie.api.runtime.KieSession;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class Exercise01ComparisonOperationTest {
|
||||
|
||||
/* contains */
|
||||
@Test
|
||||
void test1() {
|
||||
KieServices kieServices = KieServices.Factory.get();
|
||||
KieContainer container = kieServices.getKieClasspathContainer();
|
||||
KieBase kieBase = container.getKieBase("Exercise");
|
||||
KieSession session = kieBase.newKieSession();
|
||||
|
||||
ComparisonOperation zxc = ComparisonOperation.builder().field("张三名称").build();
|
||||
zxc.setOperatorList(List.of(zxc.getField()));
|
||||
|
||||
// 插入匹配
|
||||
session.insert(zxc);
|
||||
session.fireAllRules();
|
||||
session.dispose();
|
||||
|
||||
session.close();
|
||||
}
|
||||
|
||||
/* not contains */
|
||||
@Test
|
||||
void test2() {
|
||||
KieServices kieServices = KieServices.Factory.get();
|
||||
KieContainer container = kieServices.getKieClasspathContainer();
|
||||
KieBase kieBase = container.getKieBase("Exercise");
|
||||
KieSession session = kieBase.newKieSession();
|
||||
|
||||
ComparisonOperation zxc = ComparisonOperation.builder().field("qwe").build();
|
||||
zxc.setOperatorList(List.of(zxc.getField()));
|
||||
|
||||
// 插入匹配
|
||||
session.insert(zxc);
|
||||
session.fireAllRules();
|
||||
session.dispose();
|
||||
|
||||
session.close();
|
||||
}
|
||||
|
||||
/* memberOf */
|
||||
@Test
|
||||
void test3() {
|
||||
KieServices kieServices = KieServices.Factory.get();
|
||||
KieContainer container = kieServices.getKieClasspathContainer();
|
||||
KieBase kieBase = container.getKieBase("Exercise");
|
||||
KieSession session = kieBase.newKieSession();
|
||||
|
||||
ComparisonOperation zxc = ComparisonOperation.builder().field("qwe").build();
|
||||
zxc.setOperatorList(List.of(zxc.getField(), "sss"));
|
||||
|
||||
// 插入匹配
|
||||
session.insert(zxc);
|
||||
session.fireAllRules();
|
||||
session.dispose();
|
||||
|
||||
session.close();
|
||||
}
|
||||
|
||||
/* matches & not matches */
|
||||
@Test
|
||||
void test4() {
|
||||
KieServices kieServices = KieServices.Factory.get();
|
||||
KieContainer container = kieServices.getKieClasspathContainer();
|
||||
KieBase kieBase = container.getKieBase("Exercise");
|
||||
KieSession session = kieBase.newKieSession();
|
||||
|
||||
ComparisonOperation zxc = ComparisonOperation.builder().field("qwe").build();
|
||||
zxc.setOperatorList(List.of(zxc.getField(), "sss"));
|
||||
|
||||
// 插入匹配
|
||||
session.insert(zxc);
|
||||
session.fireAllRules();
|
||||
session.dispose();
|
||||
|
||||
session.close();
|
||||
}
|
||||
|
||||
/* 指定规则后缀进行匹配 */
|
||||
@Test
|
||||
void test5() {
|
||||
KieServices kieServices = KieServices.Factory.get();
|
||||
KieContainer container = kieServices.getKieClasspathContainer();
|
||||
KieBase kieBase = container.getKieBase("Exercise");
|
||||
KieSession session = kieBase.newKieSession();
|
||||
|
||||
ComparisonOperation zxc = ComparisonOperation.builder().field("qwe").build();
|
||||
zxc.setOperatorList(List.of(zxc.getField(), "sss"));
|
||||
|
||||
// 插入匹配
|
||||
session.insert(zxc);
|
||||
session.fireAllRules(new RuleNameEndsWithAgendaFilter("exercise-04-contains"));
|
||||
session.dispose();
|
||||
|
||||
session.close();
|
||||
}
|
||||
|
||||
/* 以什么前缀匹配 */
|
||||
@Test
|
||||
void test6() {
|
||||
KieServices kieServices = KieServices.Factory.get();
|
||||
KieContainer container = kieServices.getKieClasspathContainer();
|
||||
KieBase kieBase = container.getKieBase("Exercise");
|
||||
KieSession session = kieBase.newKieSession();
|
||||
|
||||
ComparisonOperation zxc = ComparisonOperation.builder().field("qwe").build();
|
||||
zxc.setOperatorList(List.of(zxc.getField(), "sss"));
|
||||
|
||||
// 插入匹配
|
||||
session.insert(zxc);
|
||||
session.fireAllRules(new RuleNameStartsWithAgendaFilter("exercise-0"));
|
||||
session.dispose();
|
||||
session.close();
|
||||
}
|
||||
|
||||
/* 以正则匹配regexp */
|
||||
@Test
|
||||
void test7() {
|
||||
KieServices kieServices = KieServices.Factory.get();
|
||||
KieContainer container = kieServices.getKieClasspathContainer();
|
||||
KieBase kieBase = container.getKieBase("Exercise");
|
||||
KieSession session = kieBase.newKieSession();
|
||||
|
||||
ComparisonOperation zxc = ComparisonOperation.builder().field("qwe").build();
|
||||
zxc.setOperatorList(List.of(zxc.getField(), "sss"));
|
||||
|
||||
// 插入匹配
|
||||
session.insert(zxc);
|
||||
session.fireAllRules(new RuleNameMatchesAgendaFilter("exercise-0.*?"));
|
||||
session.dispose();
|
||||
|
||||
session.close();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
package cn.bunny.drools.tutorials;
|
||||
|
||||
import org.kie.api.KieBase;
|
||||
import org.kie.api.KieServices;
|
||||
import org.kie.api.builder.ReleaseId;
|
||||
import org.kie.api.runtime.KieContainer;
|
||||
|
||||
public class _01InitDroolsTest {
|
||||
public static void main(String[] args) {
|
||||
KieServices kieServices = KieServices.Factory.get();
|
||||
KieContainer container = kieServices.getKieClasspathContainer();
|
||||
|
||||
ClassLoader classLoader = container.getClassLoader();
|
||||
ReleaseId releaseId = container.getReleaseId();
|
||||
|
||||
|
||||
KieBase kieBase = container.getKieBase();
|
||||
// KieBase kieBase1 = container.newKieBase();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
package cn.bunny.drools.tutorials;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.kie.api.KieBase;
|
||||
import org.kie.api.KieServices;
|
||||
import org.kie.api.runtime.KieContainer;
|
||||
import org.kie.api.runtime.KieSession;
|
||||
import org.kie.api.runtime.StatelessKieSession;
|
||||
|
||||
@Slf4j
|
||||
public class _02GetKModuleXmlTest {
|
||||
public static void main(String[] args) {
|
||||
KieServices kieServices = KieServices.Factory.get();
|
||||
KieContainer container = kieServices.getKieClasspathContainer();
|
||||
|
||||
// 验证所有可用KieBase
|
||||
log.info("Available KieBases: {}", container.getKieBaseNames());
|
||||
|
||||
KieBase kieBase = container.getKieBase("KBase1");
|
||||
log.info("kieBase--->: {}", kieBase);
|
||||
|
||||
KieSession kieSession = container.newKieSession("KSession2_1");
|
||||
log.info("kieSession--->: {}", kieSession);
|
||||
|
||||
StatelessKieSession kSession2_2 = container.newStatelessKieSession("KSession2_2");
|
||||
log.info("kSession2_2--->: {}", kSession2_2);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
package cn.bunny.drools.tutorials;
|
||||
|
||||
import lombok.SneakyThrows;
|
||||
import org.kie.api.KieServices;
|
||||
import org.kie.api.builder.KieFileSystem;
|
||||
import org.kie.api.builder.model.KieBaseModel;
|
||||
import org.kie.api.builder.model.KieModuleModel;
|
||||
import org.kie.api.builder.model.KieSessionModel;
|
||||
import org.kie.api.conf.EqualityBehaviorOption;
|
||||
import org.kie.api.conf.EventProcessingOption;
|
||||
import org.kie.api.runtime.conf.ClockTypeOption;
|
||||
|
||||
public class _03KieModuleTest {
|
||||
/**
|
||||
* 动态化规则管理
|
||||
* 传统限制:kmodule.xml声明式配置需预定义规则路径,修改需重新部署
|
||||
* 编程优势:通过KieFileSystem/KieModuleModel可实现:
|
||||
* 运行时动态加载规则(如从数据库/网络获取DRL文件)
|
||||
* 热更新规则而不重启服务(结合KieScanner)
|
||||
*/
|
||||
@SneakyThrows
|
||||
public static void main(String[] args) {
|
||||
KieServices kieServices = KieServices.Factory.get();
|
||||
KieModuleModel kieModuleModel = kieServices.newKieModuleModel();
|
||||
|
||||
KieBaseModel kBase1 = kieModuleModel.newKieBaseModel("kBase1")
|
||||
.setDefault(true)
|
||||
.setEqualsBehavior(EqualityBehaviorOption.EQUALITY)
|
||||
.setEventProcessingMode(EventProcessingOption.STREAM);
|
||||
|
||||
kBase1.newKieSessionModel("KSession1")
|
||||
.setDefault(true)
|
||||
.setType(KieSessionModel.KieSessionType.STATEFUL)
|
||||
.setClockType(ClockTypeOption.get("realtime"));
|
||||
|
||||
KieFileSystem kfs = kieServices.newKieFileSystem();
|
||||
kfs.writeKModuleXML(kieModuleModel.toXML());
|
||||
|
||||
|
||||
System.out.println(kieModuleModel.toXML());
|
||||
|
||||
Thread.sleep(10000000);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
[*.{js,jsx,mjs,cjs,ts,tsx,mts,cts,vue,css,scss,sass,less,styl}]
|
||||
charset = utf-8
|
||||
indent_size = 2
|
||||
indent_style = space
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
end_of_line = lf
|
||||
max_line_length = 100
|
|
@ -0,0 +1 @@
|
|||
[{"D:\\Project\\Study\\Code\\Web\\vue-tutorials\\src\\App.vue":"1","D:\\Project\\Study\\Code\\Web\\vue-tutorials\\src\\main.ts":"2","D:\\Project\\Study\\Code\\Web\\vue-tutorials\\src\\router\\index.ts":"3","D:\\Project\\Study\\Code\\Web\\vue-tutorials\\src\\stores\\counter.ts":"4","D:\\Project\\Study\\Code\\Web\\vue-tutorials\\src\\views\\AboutView.vue":"5","D:\\Project\\Study\\Code\\Web\\vue-tutorials\\src\\views\\HomeView.vue":"6"},{"size":1073,"mtime":1747308619237,"results":"7","hashOfConfig":"8"},{"size":251,"mtime":1747308619237,"results":"9","hashOfConfig":"10"},{"size":596,"mtime":1747308619237,"results":"11","hashOfConfig":"10"},{"size":313,"mtime":1747308619237,"results":"12","hashOfConfig":"10"},{"size":220,"mtime":1747308086660,"results":"13","hashOfConfig":"8"},{"size":100,"mtime":1747308280013,"results":"14","hashOfConfig":"8"},{"filePath":"15","messages":"16","suppressedMessages":"17","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},"v5kcfw",{"filePath":"18","messages":"19","suppressedMessages":"20","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},"ou3ylh",{"filePath":"21","messages":"22","suppressedMessages":"23","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"24","messages":"25","suppressedMessages":"26","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"27","messages":"28","suppressedMessages":"29","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"30","messages":"31","suppressedMessages":"32","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},"D:\\Project\\Study\\Code\\Web\\vue-tutorials\\src\\App.vue",[],[],"D:\\Project\\Study\\Code\\Web\\vue-tutorials\\src\\main.ts",[],[],"D:\\Project\\Study\\Code\\Web\\vue-tutorials\\src\\router\\index.ts",[],[],"D:\\Project\\Study\\Code\\Web\\vue-tutorials\\src\\stores\\counter.ts",[],[],"D:\\Project\\Study\\Code\\Web\\vue-tutorials\\src\\views\\AboutView.vue",[],[],"D:\\Project\\Study\\Code\\Web\\vue-tutorials\\src\\views\\HomeView.vue",[],[]]
|
|
@ -0,0 +1 @@
|
|||
* text=auto eol=lf
|
|
@ -0,0 +1,11 @@
|
|||
dist
|
||||
node_modules
|
||||
public
|
||||
.husky
|
||||
.vscode
|
||||
.idea
|
||||
*.sh
|
||||
*.md
|
||||
|
||||
src/assets
|
||||
stats.html
|
|
@ -0,0 +1,46 @@
|
|||
export default {
|
||||
// (x)=>{},单个参数箭头函数是否显示小括号。(always:始终显示;avoid:省略括号。默认:always)
|
||||
arrowParens: 'always',
|
||||
// 开始标签的右尖括号是否跟随在最后一行属性末尾,默认false
|
||||
bracketSameLine: false,
|
||||
// 对象字面量的括号之间打印空格 (true - Example: { foo: bar } ; false - Example: {foo:bar})
|
||||
bracketSpacing: true,
|
||||
// 是否格式化一些文件中被嵌入的代码片段的风格(auto|off;默认auto)
|
||||
embeddedLanguageFormatting: 'auto',
|
||||
// 指定 HTML 文件的空格敏感度 (css|strict|ignore;默认css)
|
||||
htmlWhitespaceSensitivity: 'ignore',
|
||||
// 当文件已经被 Prettier 格式化之后,是否会在文件顶部插入一个特殊的 @format 标记,默认false
|
||||
insertPragma: false,
|
||||
// 在 JSX 中使用单引号替代双引号,默认false
|
||||
jsxSingleQuote: false,
|
||||
// 每行最多字符数量,超出换行(默认100)
|
||||
printWidth: 100,
|
||||
// 超出打印宽度 (always | never | preserve )
|
||||
proseWrap: 'preserve',
|
||||
// 对象属性是否使用引号(as-needed | consistent | preserve;默认as-needed:对象的属性需要加引号才添加;)
|
||||
quoteProps: 'as-needed',
|
||||
// 是否只格式化在文件顶部包含特定注释(@prettier| @format)的文件,默认false
|
||||
requirePragma: false,
|
||||
// 结尾添加分号
|
||||
semi: true,
|
||||
// 使用单引号 (true:单引号;false:双引号)
|
||||
singleQuote: true,
|
||||
// 缩进空格数,默认2个空格
|
||||
tabWidth: 2,
|
||||
// 元素末尾是否加逗号,默认es5: ES5中的 objects, arrays 等会添加逗号,TypeScript 中的 type 后不加逗号
|
||||
trailingComma: 'es5',
|
||||
// 指定缩进方式,空格或tab,默认false,即使用空格
|
||||
useTabs: false,
|
||||
// vue 文件中是否缩进 <style> 和 <script> 标签,默认 false
|
||||
vueIndentScriptAndStyle: false,
|
||||
|
||||
endOfLine: 'auto',
|
||||
overrides: [
|
||||
{
|
||||
files: '*.html',
|
||||
options: {
|
||||
parser: 'html',
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
|
@ -0,0 +1,39 @@
|
|||
# vue-tutorials
|
||||
|
||||
This template should help get you started developing with Vue 3 in Vite.
|
||||
|
||||
## Recommended IDE Setup
|
||||
|
||||
[VSCode](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur).
|
||||
|
||||
## Type Support for `.vue` Imports in TS
|
||||
|
||||
TypeScript cannot handle type information for `.vue` imports by default, so we replace the `tsc` CLI with `vue-tsc` for type checking. In editors, we need [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) to make the TypeScript language service aware of `.vue` types.
|
||||
|
||||
## Customize configuration
|
||||
|
||||
See [Vite Configuration Reference](https://vite.dev/config/).
|
||||
|
||||
## Project Setup
|
||||
|
||||
```sh
|
||||
pnpm install
|
||||
```
|
||||
|
||||
### Compile and Hot-Reload for Development
|
||||
|
||||
```sh
|
||||
pnpm dev
|
||||
```
|
||||
|
||||
### Type-Check, Compile and Minify for Production
|
||||
|
||||
```sh
|
||||
pnpm build
|
||||
```
|
||||
|
||||
### Lint with [ESLint](https://eslint.org/)
|
||||
|
||||
```sh
|
||||
pnpm lint
|
||||
```
|
|
@ -0,0 +1 @@
|
|||
/// <reference types="vite/client" />
|
|
@ -0,0 +1,174 @@
|
|||
import js from '@eslint/js';
|
||||
import pluginTypeScript from '@typescript-eslint/eslint-plugin';
|
||||
import * as parserTypeScript from '@typescript-eslint/parser';
|
||||
import configPrettier from 'eslint-config-prettier';
|
||||
import { defineFlatConfig } from 'eslint-define-config';
|
||||
import pluginPrettier from 'eslint-plugin-prettier';
|
||||
import eslintPluginSimpleImportSort from 'eslint-plugin-simple-import-sort';
|
||||
import pluginVue from 'eslint-plugin-vue';
|
||||
import * as parserVue from 'vue-eslint-parser';
|
||||
|
||||
export default defineFlatConfig([
|
||||
{
|
||||
...js.configs.recommended,
|
||||
ignores: ['**/.*', 'dist/*', '*.d.ts', 'public/*', 'src/assets/**', 'src/**/iconfont/**'],
|
||||
languageOptions: {
|
||||
globals: {
|
||||
// index.d.ts
|
||||
RefType: 'readonly',
|
||||
EmitType: 'readonly',
|
||||
TargetContext: 'readonly',
|
||||
ComponentRef: 'readonly',
|
||||
ElRef: 'readonly',
|
||||
ForDataType: 'readonly',
|
||||
AnyFunction: 'readonly',
|
||||
PropType: 'readonly',
|
||||
Writable: 'readonly',
|
||||
Nullable: 'readonly',
|
||||
NonNullable: 'readonly',
|
||||
Recordable: 'readonly',
|
||||
ReadonlyRecordable: 'readonly',
|
||||
Indexable: 'readonly',
|
||||
DeepPartial: 'readonly',
|
||||
Without: 'readonly',
|
||||
Exclusive: 'readonly',
|
||||
TimeoutHandle: 'readonly',
|
||||
IntervalHandle: 'readonly',
|
||||
Effect: 'readonly',
|
||||
ChangeEvent: 'readonly',
|
||||
WheelEvent: 'readonly',
|
||||
ImportMetaEnv: 'readonly',
|
||||
Fn: 'readonly',
|
||||
PromiseFn: 'readonly',
|
||||
ComponentElRef: 'readonly',
|
||||
parseInt: 'readonly',
|
||||
parseFloat: 'readonly',
|
||||
},
|
||||
},
|
||||
plugins: {
|
||||
prettier: pluginPrettier,
|
||||
'simple-import-sort': eslintPluginSimpleImportSort,
|
||||
},
|
||||
rules: {
|
||||
...configPrettier.rules,
|
||||
'simple-import-sort/imports': 'error',
|
||||
'no-debugger': 'off',
|
||||
'no-unused-vars': [
|
||||
'error',
|
||||
{
|
||||
argsIgnorePattern: '^_',
|
||||
varsIgnorePattern: '^_',
|
||||
},
|
||||
],
|
||||
'prettier/prettier': [
|
||||
'error',
|
||||
{
|
||||
endOfLine: 'auto',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ['**/*.?([cm])ts', '**/*.?([cm])tsx'],
|
||||
languageOptions: {
|
||||
parser: parserTypeScript,
|
||||
parserOptions: {
|
||||
sourceType: 'module',
|
||||
},
|
||||
},
|
||||
plugins: {
|
||||
'@typescript-eslint': pluginTypeScript,
|
||||
},
|
||||
rules: {
|
||||
...pluginTypeScript.configs.strict.rules,
|
||||
'@typescript-eslint/ban-types': 'off',
|
||||
'@typescript-eslint/no-redeclare': 'off',
|
||||
'@typescript-eslint/ban-ts-comment': 'off',
|
||||
'@typescript-eslint/no-explicit-any': 'off',
|
||||
'@typescript-eslint/prefer-as-const': 'warn',
|
||||
'@typescript-eslint/no-empty-function': 'off',
|
||||
'@typescript-eslint/no-non-null-assertion': 'off',
|
||||
'@typescript-eslint/no-import-type-side-effects': 'error',
|
||||
'@typescript-eslint/prefer-literal-enum-member': 'off',
|
||||
'@typescript-eslint/explicit-module-boundary-types': 'off',
|
||||
'@typescript-eslint/consistent-type-imports': [
|
||||
'error',
|
||||
{
|
||||
disallowTypeAnnotations: false,
|
||||
fixStyle: 'inline-type-imports',
|
||||
},
|
||||
],
|
||||
'@typescript-eslint/no-unused-vars': [
|
||||
'error',
|
||||
{
|
||||
argsIgnorePattern: '^_',
|
||||
varsIgnorePattern: '^_',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ['**/*.d.ts'],
|
||||
rules: {
|
||||
'eslint-comments/no-unlimited-disable': 'off',
|
||||
'import/no-duplicates': 'off',
|
||||
'unused-imports/no-unused-vars': 'off',
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ['**/*.?([cm])js'],
|
||||
rules: {
|
||||
'@typescript-eslint/no-require-imports': 'off',
|
||||
'@typescript-eslint/no-var-requires': 'off',
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ['**/*.vue'],
|
||||
languageOptions: {
|
||||
globals: {
|
||||
$: 'readonly',
|
||||
$$: 'readonly',
|
||||
$computed: 'readonly',
|
||||
$customRef: 'readonly',
|
||||
$ref: 'readonly',
|
||||
$shallowRef: 'readonly',
|
||||
$toRef: 'readonly',
|
||||
},
|
||||
parser: parserVue,
|
||||
parserOptions: {
|
||||
ecmaFeatures: {
|
||||
jsx: true,
|
||||
},
|
||||
extraFileExtensions: ['.vue'],
|
||||
parser: '@typescript-eslint/parser',
|
||||
sourceType: 'module',
|
||||
},
|
||||
},
|
||||
plugins: {
|
||||
vue: pluginVue,
|
||||
},
|
||||
processor: pluginVue.processors['.vue'],
|
||||
rules: {
|
||||
...pluginVue.configs.base.rules,
|
||||
'no-undef': 'off',
|
||||
'no-unused-vars': 'off',
|
||||
'vue/no-v-html': 'off',
|
||||
'vue/require-default-prop': 'off',
|
||||
'vue/require-explicit-emits': 'off',
|
||||
'vue/multi-word-component-names': 'off',
|
||||
'vue/no-setup-props-reactivity-loss': 'off',
|
||||
'vue/html-self-closing': [
|
||||
'error',
|
||||
{
|
||||
html: {
|
||||
void: 'always',
|
||||
normal: 'always',
|
||||
component: 'always',
|
||||
},
|
||||
svg: 'always',
|
||||
math: 'always',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
]);
|
|
@ -0,0 +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="/src/main.ts"></script>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,49 @@
|
|||
{
|
||||
"name": "vue-tutorials",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "run-p type-check \"build-only {@}\" --",
|
||||
"preview": "vite preview",
|
||||
"build-only": "vite build",
|
||||
"type-check": "vue-tsc --build",
|
||||
"lint:eslint": "eslint --cache --max-warnings 0 \"{src,mock,build}/**/*.{vue,js,ts,tsx}\" --fix",
|
||||
"lint:prettier": "prettier --write \"src/**/*.{js,ts,json,tsx,css,scss,vue,html,md}\"",
|
||||
"lint:stylelint": "stylelint --cache --fix \"**/*.{html,vue,css,scss}\" --cache-location node_modules/.cache/stylelint/",
|
||||
"lint": "pnpm lint:eslint && pnpm lint:prettier",
|
||||
"format": "prettier --write src/"
|
||||
},
|
||||
"dependencies": {
|
||||
"@eslint/js": "^9.26.0",
|
||||
"@typescript-eslint/eslint-plugin": "^8.32.1",
|
||||
"@typescript-eslint/parser": "^8.32.1",
|
||||
"eslint-config-prettier": "^10.1.5",
|
||||
"eslint-define-config": "^2.1.0",
|
||||
"eslint-plugin-prettier": "^5.4.0",
|
||||
"eslint-plugin-simple-import-sort": "^12.1.1",
|
||||
"pinia": "^3.0.1",
|
||||
"vue": "^3.5.13",
|
||||
"vue-eslint-parser": "^10.1.3",
|
||||
"vue-router": "^4.5.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tsconfig/node22": "^22.0.1",
|
||||
"@types/node": "^22.14.0",
|
||||
"@vitejs/plugin-vue": "^5.2.3",
|
||||
"@vitejs/plugin-vue-jsx": "^4.1.2",
|
||||
"@vue/eslint-config-prettier": "^10.2.0",
|
||||
"@vue/eslint-config-typescript": "^14.5.0",
|
||||
"@vue/tsconfig": "^0.7.0",
|
||||
"eslint": "^9.22.0",
|
||||
"eslint-plugin-vue": "~10.0.0",
|
||||
"jiti": "^2.4.2",
|
||||
"npm-run-all2": "^7.0.2",
|
||||
"prettier": "3.5.3",
|
||||
"typescript": "~5.8.0",
|
||||
"vite": "^6.2.4",
|
||||
"vite-plugin-vue-devtools": "^7.7.2",
|
||||
"vue-tsc": "^2.2.8"
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
Binary file not shown.
After Width: | Height: | Size: 4.2 KiB |
|
@ -0,0 +1,71 @@
|
|||
<script setup lang="ts">
|
||||
import { RouterView } from 'vue-router';
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<RouterView />
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
header {
|
||||
line-height: 1.5;
|
||||
max-height: 100vh;
|
||||
}
|
||||
|
||||
.logo {
|
||||
display: block;
|
||||
margin: 0 auto 2rem;
|
||||
}
|
||||
|
||||
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>
|
|
@ -0,0 +1,86 @@
|
|||
/* 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;
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
<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>
|
After Width: | Height: | Size: 276 B |
|
@ -0,0 +1,35 @@
|
|||
@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;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
import './assets/main.css';
|
||||
|
||||
import { createPinia } from 'pinia';
|
||||
import { createApp } from 'vue';
|
||||
|
||||
import App from './App.vue';
|
||||
import router from './router';
|
||||
|
||||
const app = createApp(App);
|
||||
|
||||
app.use(createPinia());
|
||||
app.use(router);
|
||||
|
||||
app.mount('#app');
|
|
@ -0,0 +1,24 @@
|
|||
import { createRouter, createWebHistory } from 'vue-router';
|
||||
|
||||
import HomeView from '../views/HomeView.vue';
|
||||
|
||||
const router = createRouter({
|
||||
history: createWebHistory(import.meta.env.BASE_URL),
|
||||
routes: [
|
||||
{
|
||||
path: '/',
|
||||
name: 'home',
|
||||
component: HomeView,
|
||||
},
|
||||
{
|
||||
path: '/about',
|
||||
name: 'about',
|
||||
// route level code-splitting
|
||||
// this generates a separate chunk (About.[hash].js) for this route
|
||||
// which is lazy-loaded when the route is visited.
|
||||
component: () => import('../views/AboutView.vue'),
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
export default router;
|
|
@ -0,0 +1,12 @@
|
|||
import { defineStore } from 'pinia';
|
||||
import { computed, ref } from 'vue';
|
||||
|
||||
export const useCounterStore = defineStore('counter', () => {
|
||||
const count = ref(0);
|
||||
const doubleCount = computed(() => count.value * 2);
|
||||
function increment() {
|
||||
count.value++;
|
||||
}
|
||||
|
||||
return { count, doubleCount, increment };
|
||||
});
|
|
@ -0,0 +1,61 @@
|
|||
<script setup lang="tsx">
|
||||
import { ref } from 'vue';
|
||||
|
||||
defineProps({
|
||||
peiqi: Number,
|
||||
modelValue: Number,
|
||||
timestamp: String,
|
||||
});
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'update:modelValue', value: number): void;
|
||||
(e: 'update:peiqi123', value: number): void;
|
||||
}>();
|
||||
|
||||
// 定义子组件的值,传给父组件
|
||||
const childCount = ref(99);
|
||||
|
||||
// 定义子组件的值,传给父组件 --- 2
|
||||
const childCount2 = ref(888);
|
||||
|
||||
/* 修改值 */
|
||||
const onClick = () => {
|
||||
childCount.value++;
|
||||
childCount2.value++;
|
||||
|
||||
// 第一种,子传父
|
||||
emit('update:modelValue', childCount.value);
|
||||
// 第二种,子传父
|
||||
emit('update:peiqi123', childCount2.value);
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="about">
|
||||
<h1>子页面</h1>
|
||||
|
||||
<p>childCount:{{ childCount }}</p>
|
||||
<p>childCount2:{{ childCount2 }}</p>
|
||||
<p @click="onClick">时间戳:{{ timestamp }}</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
@media (min-width: 1024px) {
|
||||
.about {
|
||||
min-height: 50vh;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
|
||||
h1 {
|
||||
display: block;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
p {
|
||||
font-weight: 800;
|
||||
font-size: 24px;
|
||||
color: purple;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,48 @@
|
|||
<script setup lang="ts">
|
||||
import { onMounted, ref } from 'vue';
|
||||
|
||||
import AboutView from '@/views/AboutView.vue';
|
||||
|
||||
// 父传子
|
||||
const timestamp = ref(new Date().getTime() + '');
|
||||
|
||||
// 父组件的count
|
||||
const parentCount = ref(0);
|
||||
const parentCount2 = ref(444);
|
||||
|
||||
onMounted(() => {
|
||||
setInterval(() => {
|
||||
timestamp.value = new Date().getTime() + '';
|
||||
}, 1000);
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<main>
|
||||
<div class="parent">
|
||||
<h1>父页面</h1>
|
||||
|
||||
<p>parentCount:{{ parentCount }}</p>
|
||||
<p>parentCount2:{{ parentCount2 }}</p>
|
||||
</div>
|
||||
|
||||
<AboutView :timestamp="timestamp" v-model="parentCount" v-model:peiqi123="parentCount2" />
|
||||
</main>
|
||||
</template>
|
||||
|
||||
<style scoped lang="css">
|
||||
main {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
p {
|
||||
color: yellowgreen;
|
||||
}
|
||||
|
||||
.parent {
|
||||
min-height: 50vh;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"extends": "@vue/tsconfig/tsconfig.dom.json",
|
||||
"include": ["env.d.ts", "src/**/*", "src/**/*.vue"],
|
||||
"exclude": ["src/**/__tests__/*"],
|
||||
"compilerOptions": {
|
||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
|
||||
|
||||
"paths": {
|
||||
"@/*": ["./src/*"]
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"files": [],
|
||||
"references": [
|
||||
{
|
||||
"path": "./tsconfig.node.json"
|
||||
},
|
||||
{
|
||||
"path": "./tsconfig.app.json"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
{
|
||||
"extends": "@tsconfig/node22/tsconfig.json",
|
||||
"include": [
|
||||
"vite.config.*",
|
||||
"vitest.config.*",
|
||||
"cypress.config.*",
|
||||
"nightwatch.conf.*",
|
||||
"playwright.config.*",
|
||||
"eslint.config.*"
|
||||
],
|
||||
"compilerOptions": {
|
||||
"noEmit": true,
|
||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
|
||||
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "Bundler",
|
||||
"types": ["node"]
|
||||
}
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
import { fileURLToPath, URL } from 'node:url';
|
||||
|
||||
import vue from '@vitejs/plugin-vue';
|
||||
import vueJsx from '@vitejs/plugin-vue-jsx';
|
||||
import { defineConfig } from 'vite';
|
||||
import vueDevTools from 'vite-plugin-vue-devtools';
|
||||
|
||||
// https://vite.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [vue(), vueJsx(), vueDevTools()],
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': fileURLToPath(new URL('./src', import.meta.url)),
|
||||
},
|
||||
},
|
||||
});
|
Loading…
Reference in New Issue