This commit is contained in:
bunny 2025-05-17 20:32:12 +08:00
commit fb4134cc27
63 changed files with 6203 additions and 0 deletions

65
.gitignore vendored Normal file
View File

@ -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

49
drools/.gitlab-ci.yml Normal file
View File

@ -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."

29
drools/Dockerfile Normal file
View File

@ -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

256
drools/Gitlab安装.md Normal file
View File

@ -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-cehttps://mirrors.tuna.tsinghua.edu.cn/gitlab-ce/ubuntu/pool/focal/main/g/gitlab-ce/
>
> gitlab-runner-helper-imageshttps://mirrors.tuna.tsinghua.edu.cn/gitlab-runner/ubuntu/pool/focal/main/g/gitlab-runner-helper-images/
>
> gitlab-runnerhttps://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."
```

3
drools/ReadMe.md Normal file
View File

@ -0,0 +1,3 @@
# Drools
包含Gitlab部署

126
drools/pom.xml Normal file
View File

@ -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>

5
drools/push.sh Normal file
View File

@ -0,0 +1,5 @@
git checkout master
git merge dev
git push --all
git push --tags
git checkout dev

View File

@ -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);
}
}

View File

@ -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;
}

View File

@ -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);
}
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -0,0 +1,8 @@
package cn.bunny.drools.bean.demo.demo2;
/* 类容类型 */
public enum ContentType {
GENERAL, // 普通内容
R_RATED, // R级内容
ALCOHOL // 酒精类产品
}

View File

@ -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;
}

View File

@ -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";
}

View File

@ -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;
}
}

View File

@ -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;
}

View File

@ -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();
}
}

View File

@ -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>

View File

@ -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

View File

@ -0,0 +1,10 @@
_ _
| |__ _ _ _ __ _ __ _ _ (_) __ ___ ____ _
| '_ \| | | | '_ \| '_ \| | | | | |/ _` \ \ / / _` |
| |_) | |_| | | | | | | | |_| | | | (_| |\ V | (_| |
|_.__/ \__,_|_| |_|_| |_|\__, | _/ |\__,_| \_/ \__,_|
|___/ |__/
Service Name${spring.application.name}
SpringBoot Version: ${spring-boot.version}${spring-boot.formatted-version}
SpringActive${spring.profiles.active}

View File

@ -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>

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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();
}
}

View File

@ -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);
}
}
}

View File

@ -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());
}
}

View File

@ -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;
}
}
}

View File

@ -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();
}
}

View File

@ -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();
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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

View File

@ -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",[],[]]

1
vue-tutorials/.gitattributes vendored Normal file
View File

@ -0,0 +1 @@
* text=auto eol=lf

View File

@ -0,0 +1,11 @@
dist
node_modules
public
.husky
.vscode
.idea
*.sh
*.md
src/assets
stats.html

View File

@ -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',
},
},
],
};

39
vue-tutorials/README.md Normal file
View File

@ -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
```

1
vue-tutorials/env.d.ts vendored Normal file
View File

@ -0,0 +1 @@
/// <reference types="vite/client" />

View File

@ -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',
},
],
},
},
]);

13
vue-tutorials/index.html Normal file
View File

@ -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>

View File

@ -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"
}
}

3843
vue-tutorials/pnpm-lock.yaml Normal file

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

71
vue-tutorials/src/App.vue Normal file
View File

@ -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>

View File

@ -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;
}

View File

@ -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

View File

@ -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;
}
}

14
vue-tutorials/src/main.ts Normal file
View File

@ -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');

View File

@ -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;

View File

@ -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 };
});

View File

@ -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>

View File

@ -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>

View File

@ -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/*"]
}
}
}

View File

@ -0,0 +1,11 @@
{
"files": [],
"references": [
{
"path": "./tsconfig.node.json"
},
{
"path": "./tsconfig.app.json"
}
]
}

View File

@ -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"]
}
}

View File

@ -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)),
},
},
});