第一次上传

This commit is contained in:
xxk
2026-06-11 10:31:24 +08:00
commit cfef094568
1523 changed files with 210650 additions and 0 deletions
+35
View File
@@ -0,0 +1,35 @@
# EditorConfig is awesome: https://EditorConfig.org
# top-most EditorConfig file
root = true
# Unix-style newlines with a newline ending every file
[*]
charset = utf-8
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
# Java files
[*.java]
indent_style = space
indent_size = 4
# XML files
[*.xml]
indent_style = space
indent_size = 4
# YAML files
[*.{yml,yaml}]
indent_style = space
indent_size = 2
# Properties files
[*.properties]
indent_style = space
indent_size = 4
# Markdown files
[*.md]
trim_trailing_whitespace = false
+8
View File
@@ -0,0 +1,8 @@
# Default ignored files
/shelf/
/workspace.xml
# Editor-based HTTP Client requests
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml
+18
View File
@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CompilerConfiguration">
<annotationProcessing>
<profile name="Maven default annotation processors profile" enabled="true">
<sourceOutputDir name="target/generated-sources/annotations" />
<sourceTestOutputDir name="target/generated-test-sources/test-annotations" />
<outputRelativeToContentRoot value="true" />
<module name="youlai-boot" />
</profile>
</annotationProcessing>
</component>
<component name="JavacSettings">
<option name="ADDITIONAL_OPTIONS_OVERRIDE">
<module name="youlai-boot" options="-parameters" />
</option>
</component>
</project>
+6
View File
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Encoding">
<file url="file://$PROJECT_DIR$/src/main/java" charset="UTF-8" />
</component>
</project>
+20
View File
@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="RemoteRepositoriesConfiguration">
<remote-repository>
<option name="id" value="central" />
<option name="name" value="Central Repository" />
<option name="url" value="http://maven.aliyun.com/nexus/content/groups/public/" />
</remote-repository>
<remote-repository>
<option name="id" value="central" />
<option name="name" value="Maven Central repository" />
<option name="url" value="https://repo1.maven.org/maven2" />
</remote-repository>
<remote-repository>
<option name="id" value="jboss.community" />
<option name="name" value="JBoss Community repository" />
<option name="url" value="https://repository.jboss.org/nexus/content/repositories/public/" />
</remote-repository>
</component>
</project>
+12
View File
@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="MavenProjectsManager">
<option name="originalFiles">
<list>
<option value="$PROJECT_DIR$/pom.xml" />
</list>
</option>
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" default="true" project-jdk-name="17" project-jdk-type="JavaSDK" />
</project>
+201
View File
@@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright 2023-present 有来开源
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
+69
View File
@@ -0,0 +1,69 @@
# 创建一个名为 "youlai-boot" 的桥接网络,在同一个网络中的容器可以通过容器名互相访问
networks:
youlai-boot:
driver: bridge
services:
mysql:
image: mysql:8.0.29
container_name: mysql
restart: unless-stopped # 重启策略:除非手动停止容器,否则自动重启
environment:
- TZ=Asia/Shanghai
- LANG= en_US.UTF-8
- MYSQL_ROOT_PASSWORD=123456 #设置 root 用户的密码
volumes:
- ./mysql/conf/my.cnf:/etc/my.cnf # 挂载 my.cnf 文件到容器的指定路径
- ./mysql/data:/var/lib/mysql # 持久化 MySQL 数据
- ../sql/mysql:/docker-entrypoint-initdb.d # 初始化 SQL 脚本目录
ports:
- 3306:3306
networks:
- youlai-boot # 加入 "youlai-boot" 网络
redis:
image: redis:7.2.3
container_name: redis
restart: unless-stopped
command: redis-server /etc/redis/redis.conf --requirepass 123456 --appendonly no # 启动 Redis 服务并添加密码为:123456,默认不开启 Redis AOF 方式持久化配置
environment:
- TZ=Asia/Shanghai
volumes:
- ./redis/data:/data
- ./redis/config/redis.conf:/etc/redis/redis.conf
ports:
- 6379:6379
networks:
- youlai-boot
minio:
image: minio/minio:RELEASE.2024-07-16T23-46-41Z
container_name: minio
restart: unless-stopped
command: server /data --console-address ":9001"
ports:
- 9000:9000
- 9001:9001
environment:
- TZ=Asia/Shanghai
- LANG=en_US.UTF-8
- MINIO_ROOT_USER=minioadmin
- MINIO_ROOT_PASSWORD=minioadmin
volumes:
- ./minio/data:/data
- ./minio/config:/root/.minio
networks:
- youlai-boot
xxl-job-admin:
image: xuxueli/xxl-job-admin:2.4.0
container_name: xxl-job-admin
restart: unless-stopped
environment:
PARAMS: '--spring.datasource.url=jdbc:mysql://mysql:3306/xxl_job?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&serverTimezone=Asia/Shanghai --spring.datasource.username=root --spring.datasource.password=123456 --spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver'
volumes:
- ./xxljob/logs:/data/applogs
ports:
- 8080:8080
networks:
- youlai-boot
View File
+20
View File
@@ -0,0 +1,20 @@
[mysqld]
# 字符集与排序规则
character-set-server = utf8mb4 # 服务端默认字符集
collation-server = utf8mb4_0900_ai_ci # 服务端默认排序规则
# 网络与路径
datadir = /var/lib/mysql # 数据文件存放的目录
bind-address = 0.0.0.0 # 允许远程连接,默认 127.0.0.1 只允许本地连接
port = 3306 # 显式指定端口(默认3306可不写)
# 客户端字符集同步(避免乱码)
init_connect = 'SET NAMES utf8mb4' # 连接初始化时设置字符集
[client]
default-character-set = utf8mb4 # 客户端默认字符集
[mysql]
default-character-set = utf8mb4 # MySQL 命令行工具字符集
File diff suppressed because it is too large Load Diff
+16
View File
@@ -0,0 +1,16 @@
# Docker Compose 安装中间件 MySQL、Redis、Minio、Xxl-Job
## 安装
```bash
docker-compose -f ./docker-compose.yml -p youlai-boot up -d
```
- p youlai-boot 指定命名空间,避免与其他容器冲突,这里方便管理,统一管理和卸载
## 卸载
```bash
docker-compose -f ./docker-compose.yml -p youlai-boot down
```
View File
+298
View File
@@ -0,0 +1,298 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.youlai</groupId>
<artifactId>youlai-boot</artifactId>
<version>4.3.0</version>
<description>基于 Java 17 + SpringBoot 4 + Spring Security 构建的权限管理系统。</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>4.0.1</version> <!-- lookup parent from repository -->
<relativePath/>
</parent>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<hutool.version>5.8.41</hutool.version>
<mysql-connector-j.version>9.1.0</mysql-connector-j.version>
<druid.version>1.2.24</druid.version>
<!-- Spring Boot 4.x 必须使用更新的版本 -->
<mybatis-plus.version>3.5.15</mybatis-plus.version>
<dynamic-datasource.version>4.3.1</dynamic-datasource.version>
<knife4j.version>4.5.0</knife4j.version>
<mapstruct.version>1.6.3</mapstruct.version>
<lombok-mapstruct-binding.version>0.2.0</lombok-mapstruct-binding.version>
<xxl-job.version>3.2.0</xxl-job.version>
<fastexcel.version>1.3.0</fastexcel.version>
<!-- 对象存储 -->
<minio.version>8.5.10</minio.version>
<okhttp3.version>4.8.1</okhttp3.version>
<aliyun-sdk-oss.version>3.16.3</aliyun-sdk-oss.version>
<!-- redisson 分布式锁 -->
<redisson.version>4.1.0</redisson.version>
<!-- 自动代码生成 -->
<mybatis-plus-generator.version>3.5.6</mybatis-plus-generator.version>
<velocity.version>2.3</velocity.version>
<!-- IP 地区转换 -->
<ip2region.version>2.7.0</ip2region.version>
<!-- 阿里云短信 -->
<aliyun.java.sdk.core.version>4.7.6</aliyun.java.sdk.core.version>
<aliyun.java.sdk.dysmsapi.version>2.2.1</aliyun.java.sdk.dysmsapi.version>
<caffeine.version>2.9.3</caffeine.version>
<!-- 阿里 TransmittableThreadLocal (支持异步场景的ThreadLocal传递) -->
<transmittable-thread-local.version>2.14.5</transmittable-thread-local.version>
<weixin-java-miniapp.version>4.8.1.B</weixin-java-miniapp.version>
</properties>
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<!--编译测试环境,不打包在lib-->
<scope>provided</scope>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>${hutool.version}</version>
</dependency>
<!-- TransmittableThreadLocal: 支持异步场景的租户上下文传递 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>transmittable-thread-local</artifactId>
<version>${transmittable-thread-local.version}</version>
</dependency>
<!-- 允许使用Lombok的Java Bean类中使用MapStruct注解 (Lombok 1.18.20+) -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok-mapstruct-binding</artifactId>
<version>${lombok-mapstruct-binding.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<!-- Spring Boot 4.x 已改名 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aspectj</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mail</artifactId>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<version>${mysql-connector-j.version}</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>${druid.version}</version>
</dependency>
<!-- Spring Boot 4.x 必须使用boot4版本 -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-spring-boot4-starter</artifactId>
<version>${mybatis-plus.version}</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-jsqlparser</artifactId>
<version>${mybatis-plus.version}</version>
</dependency>
<!-- knife4j 接口文档 -->
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-openapi3-jakarta-spring-boot-starter</artifactId>
<version>${knife4j.version}</version>
<exclusions>
<exclusion>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
<version>2.8.9</version>
</dependency>
<!-- MapStruct 对象映射 -->
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
<version>${mapstruct.version}</version>
</dependency>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>${mapstruct.version}</version>
</dependency>
<!-- xxl-job 定时任务 -->
<dependency>
<groupId>com.xuxueli</groupId>
<artifactId>xxl-job-core</artifactId>
<version>${xxl-job.version}</version>
</dependency>
<!-- Excel 工具(EasyExcel-PLus -->
<dependency>
<groupId>cn.idev.excel</groupId>
<artifactId>fastexcel</artifactId>
<version>${fastexcel.version}</version>
</dependency>
<!-- MinIO 对象存储 -->
<dependency>
<groupId>io.minio</groupId>
<artifactId>minio</artifactId>
<version>${minio.version}</version>
</dependency>
<!-- 阿里云 OSS 对象存储 -->
<dependency>
<groupId>com.aliyun.oss</groupId>
<artifactId>aliyun-sdk-oss</artifactId>
<version>${aliyun-sdk-oss.version}</version>
</dependency>
<!-- redisson 分布式锁 -->
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
<version>${redisson.version}</version>
</dependency>
<!-- mybatis-plus 代码生成器 -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
<version>${mybatis-plus-generator.version}</version>
</dependency>
<!-- velocity 模板引擎(代码生成) -->
<dependency>
<groupId>org.apache.velocity</groupId>
<artifactId>velocity-engine-core</artifactId>
<version>${velocity.version}</version>
</dependency>
<!-- IP 转省市区 -->
<dependency>
<groupId>org.lionsoul</groupId>
<artifactId>ip2region</artifactId>
<version>${ip2region.version}</version>
</dependency>
<dependency>
<groupId>com.aliyun</groupId>
<artifactId>aliyun-java-sdk-core</artifactId>
<version>${aliyun.java.sdk.core.version}</version>
</dependency>
<dependency>
<groupId>com.aliyun</groupId>
<artifactId>aliyun-java-sdk-dysmsapi</artifactId>
<version>${aliyun.java.sdk.dysmsapi.version}</version>
</dependency>
<!-- 本地缓存 -->
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
<version>${caffeine.version}</version>
</dependency>
<!-- 微信小程序登录 -->
<dependency>
<groupId>com.github.binarywang</groupId>
<artifactId>weixin-java-miniapp</artifactId>
<version>${weixin-java-miniapp.version}</version>
</dependency>
<!-- 动态多数据源 -->
<!--<dependency>
<groupId>com.baomidou</groupId>
<artifactId>dynamic-datasource-spring-boot3-starter</artifactId>
<version>${dynamic-datasource.version}</version>
</dependency>-->
</dependencies>
<build>
<finalName>${project.artifactId}</finalName>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
@@ -0,0 +1,11 @@
ALTER TABLE `xxk_wallet_recharge_order`
ADD COLUMN `gift_amount` decimal(18,2) NOT NULL DEFAULT 0.00 COMMENT '充值赠送金额' AFTER `amount`,
ADD COLUMN `credited_amount` decimal(18,2) NOT NULL DEFAULT 0.00 COMMENT '实际到账金额' AFTER `gift_amount`,
ADD COLUMN `promotion_rule_snapshot` text NULL COMMENT '命中的充值活动规则快照' AFTER `credited_amount`;
UPDATE `xxk_wallet_recharge_order`
SET `gift_amount` = IFNULL(`gift_amount`, 0.00),
`credited_amount` = CASE
WHEN IFNULL(`credited_amount`, 0.00) > 0 THEN `credited_amount`
ELSE IFNULL(`amount`, 0.00) + IFNULL(`gift_amount`, 0.00)
END;
+19
View File
@@ -0,0 +1,19 @@
DROP TABLE IF EXISTS `xxk_proxy_region`;
CREATE TABLE `xxk_proxy_region` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`region_code` varchar(64) NOT NULL COMMENT '地区编号',
`region_name_zh` varchar(100) NOT NULL COMMENT '地区中文名',
`region_name` varchar(100) NOT NULL COMMENT '地区名称',
`icon_url` varchar(500) NOT NULL COMMENT '地区图标',
`status` tinyint NOT NULL DEFAULT '1' COMMENT '状态(1-启用 0-禁用)',
`sort` int NOT NULL DEFAULT '0' COMMENT '排序',
`remark` varchar(255) DEFAULT NULL COMMENT '备注',
`create_by` bigint DEFAULT NULL COMMENT '创建人',
`create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_by` bigint DEFAULT NULL COMMENT '更新人',
`update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`is_deleted` tinyint NOT NULL DEFAULT '0' COMMENT '是否删除(1-已删 0-未删)',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_proxy_region_code_deleted` (`region_code`,`is_deleted`),
KEY `idx_proxy_region_status` (`status`,`is_deleted`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='代理平台地区管理表';
+47
View File
@@ -0,0 +1,47 @@
-- 代理模式改造升级脚本
-- 适用范围:
-- 1. 将会员资料补充代理级别字段。
-- 2. 为历史会员回填默认代理级别为二级代理。
-- 3. 规范异常代理级别值,保证新代码可直接使用。
--
-- 执行建议:
-- 1. 执行前先备份数据库。
-- 2. 在业务低峰期执行。
-- 3. 执行完成后再发布本次 Java / UI / member-web 代码。
SET @current_schema = DATABASE();
-- 1) 如果 xxk_member_profile.agent_level 不存在,则新增字段
SET @has_agent_level = (
SELECT COUNT(1)
FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = @current_schema
AND TABLE_NAME = 'xxk_member_profile'
AND COLUMN_NAME = 'agent_level'
);
SET @ddl_sql = IF(
@has_agent_level = 0,
'ALTER TABLE `xxk_member_profile`
ADD COLUMN `agent_level` TINYINT NOT NULL DEFAULT 2 COMMENT ''代理级别(1一级代理 2二级代理)'' AFTER `invite_code`',
'SELECT ''xxk_member_profile.agent_level already exists'' AS message'
);
PREPARE stmt FROM @ddl_sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
-- 2) 历史数据回填:
-- 空值、0、其他非法值一律收敛成二级代理
UPDATE `xxk_member_profile`
SET `agent_level` = 2
WHERE `agent_level` IS NULL
OR `agent_level` NOT IN (1, 2);
UPDATE `sys_menu` SET `name` = '代理返佣流水' WHERE `id` = 1200
ALTER TABLE `xxk_distribution_config`
ADD COLUMN IF NOT EXISTS `withdraw_threshold` decimal(10,2) NOT NULL DEFAULT 0.00 COMMENT '提现门槛金额' AFTER `second_level_rate`,
ADD COLUMN IF NOT EXISTS `withdraw_fee_rate` decimal(10,4) NOT NULL DEFAULT 0.0000 COMMENT '提现手续费比例' AFTER `withdraw_threshold`;
@@ -0,0 +1,15 @@
-- 修复会员分销字段漏合并导致的后台用户中心查询报错。
-- 适用于已存在 xxk_distribution_relation / xxk_distribution_commission 老结构的数据库。
ALTER TABLE `xxk_distribution_relation`
ADD COLUMN `member_user_id` BIGINT NULL COMMENT '当前会员ID' AFTER `grand_parent_user_id`,
ADD COLUMN `parent_member_user_id` BIGINT NULL COMMENT '一级上级会员ID' AFTER `member_user_id`,
ADD COLUMN `grand_parent_member_user_id` BIGINT NULL COMMENT '二级上级会员ID' AFTER `parent_member_user_id`,
ADD UNIQUE INDEX `uk_xxk_distribution_relation_member_user_id` (`member_user_id`),
ADD KEY `idx_xxk_distribution_relation_parent_member_user_id` (`parent_member_user_id`);
ALTER TABLE `xxk_distribution_commission`
ADD COLUMN `member_user_id` BIGINT NULL COMMENT '佣金归属会员ID' AFTER `from_user_id`,
ADD COLUMN `from_member_user_id` BIGINT NULL COMMENT '消费会员ID' AFTER `member_user_id`,
ADD KEY `idx_xxk_distribution_commission_member_user_id` (`member_user_id`),
ADD KEY `idx_xxk_distribution_commission_from_member_user_id` (`from_member_user_id`);
@@ -0,0 +1,8 @@
-- 分销关系表 user_id 可空兼容升级脚本
-- 说明:
-- 1. 历史设计中 xxk_distribution_relation 仅面向 sys_useruser_id 为 NOT NULL。
-- 2. 会员体系接入后,会员邀请关系只依赖 member_user_id / parent_member_user_id。
-- 3. 若继续要求 user_id 非空,会员注册或邀请码绑定时会因未传 sys_user.user_id 而写库失败。
ALTER TABLE `xxk_distribution_relation`
MODIFY COLUMN `user_id` BIGINT NULL COMMENT '当前用户ID';
@@ -0,0 +1,3 @@
ALTER TABLE `xxk_distribution_config`
ADD COLUMN `withdraw_threshold` decimal(10,2) NOT NULL DEFAULT 0.00 COMMENT '提现门槛金额' AFTER `second_level_rate`,
ADD COLUMN `withdraw_fee_rate` decimal(10,4) NOT NULL DEFAULT 0.0000 COMMENT '提现手续费比例' AFTER `withdraw_threshold`;
@@ -0,0 +1,15 @@
INSERT INTO `sys_menu`
(`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`, `always_show`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`)
VALUES
(12010, 1200, '0,1100,1200', '提现申请', 'C', 'ProxyDistributionWithdraw', 'distribution-withdraw', 'proxy/distribution-withdraw/index', NULL, 0, 1, 1, 20, 'Wallet', NULL, NOW(), NOW(), NULL),
(12011, 12010, '0,1100,1200,12010', '提现申请查询', 'B', NULL, '', NULL, 'proxy:distribution:withdraw:list', NULL, NULL, 1, 1, '', NULL, NOW(), NOW(), NULL),
(12012, 12010, '0,1100,1200,12010', '提现申请审核', 'B', NULL, '', NULL, 'proxy:distribution:withdraw:audit', NULL, NULL, 1, 2, '', NULL, NOW(), NOW(), NULL),
(12013, 12010, '0,1100,1200,12010', '提现确认打款', 'B', NULL, '', NULL, 'proxy:distribution:withdraw:pay', NULL, NULL, 1, 3, '', NULL, NOW(), NOW(), NULL)
ON DUPLICATE KEY UPDATE
`name` = VALUES(`name`),
`route_path` = VALUES(`route_path`),
`component` = VALUES(`component`),
`perm` = VALUES(`perm`),
`sort` = VALUES(`sort`),
`icon` = VALUES(`icon`),
`update_time` = NOW();
@@ -0,0 +1,69 @@
-- 分销佣金提现模块升级脚本
-- 说明:
-- 1. 在分销佣金账户中增加累计提现金额。
-- 2. 在分销佣金账户流水中增加冻结余额与提现单关联字段。
-- 3. 新增会员提现收款方式表与提现申请表。
ALTER TABLE `xxk_distribution_account`
ADD COLUMN `total_withdraw_amount` DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '累计提现金额' AFTER `total_transfer_amount`;
ALTER TABLE `xxk_distribution_account_flow`
ADD COLUMN `before_frozen_balance` DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '变动前冻结余额' AFTER `after_balance`,
ADD COLUMN `after_frozen_balance` DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '变动后冻结余额' AFTER `before_frozen_balance`,
ADD COLUMN `related_withdraw_no` VARCHAR(64) DEFAULT NULL COMMENT '关联提现单号' AFTER `related_wallet_flow_no`,
ADD KEY `idx_xxk_distribution_account_flow_related_withdraw_no` (`related_withdraw_no`);
CREATE TABLE `xxk_distribution_withdraw_method` (
`id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`member_user_id` BIGINT NOT NULL COMMENT '会员ID',
`method_type` VARCHAR(32) NOT NULL COMMENT '收款方式(ALIPAY_ACCOUNT/ALIPAY_QR/WECHAT_QR)',
`account_name` VARCHAR(64) NOT NULL COMMENT '收款人姓名',
`account_no` VARCHAR(128) DEFAULT NULL COMMENT '收款账号',
`qr_code_url` VARCHAR(255) DEFAULT NULL COMMENT '收款码地址',
`is_default` TINYINT NOT NULL DEFAULT 0 COMMENT '是否默认(1-是 0-否)',
`status` TINYINT NOT NULL DEFAULT 1 COMMENT '状态(1-正常 0-禁用)',
`remark` VARCHAR(255) DEFAULT NULL COMMENT '备注',
`create_by` BIGINT DEFAULT NULL COMMENT '创建人',
`update_by` BIGINT DEFAULT NULL COMMENT '更新人',
`create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`),
KEY `idx_xxk_distribution_withdraw_method_member_user_id` (`member_user_id`),
KEY `idx_xxk_distribution_withdraw_method_default` (`member_user_id`, `is_default`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='分销提现收款方式表';
CREATE TABLE `xxk_distribution_withdraw_apply` (
`id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`withdraw_no` VARCHAR(64) NOT NULL COMMENT '提现单号',
`member_user_id` BIGINT NOT NULL COMMENT '会员ID',
`username_snapshot` VARCHAR(64) DEFAULT NULL COMMENT '会员账号快照',
`nickname_snapshot` VARCHAR(64) DEFAULT NULL COMMENT '会员昵称快照',
`mobile_snapshot` VARCHAR(32) DEFAULT NULL COMMENT '会员手机号快照',
`withdraw_method_id` BIGINT DEFAULT NULL COMMENT '提现方式ID',
`method_type` VARCHAR(32) NOT NULL COMMENT '收款方式类型',
`account_name_snapshot` VARCHAR(64) NOT NULL COMMENT '收款人快照',
`account_no_snapshot` VARCHAR(128) DEFAULT NULL COMMENT '收款账号快照',
`qr_code_url_snapshot` VARCHAR(255) DEFAULT NULL COMMENT '收款码快照',
`amount` DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '申请提现金额',
`paid_amount` DECIMAL(18,2) DEFAULT NULL COMMENT '实际打款金额',
`status` VARCHAR(32) NOT NULL COMMENT '状态(PENDING/APPROVED/REJECTED/PAID)',
`submit_remark` VARCHAR(255) DEFAULT NULL COMMENT '申请备注',
`submit_time` DATETIME DEFAULT NULL COMMENT '申请时间',
`audit_remark` VARCHAR(255) DEFAULT NULL COMMENT '审核备注',
`audit_by` BIGINT DEFAULT NULL COMMENT '审核人',
`audit_time` DATETIME DEFAULT NULL COMMENT '审核时间',
`pay_remark` VARCHAR(255) DEFAULT NULL COMMENT '打款备注',
`pay_proof_urls_json` TEXT DEFAULT NULL COMMENT '打款凭证JSON',
`pay_by` BIGINT DEFAULT NULL COMMENT '打款确认人',
`pay_time` DATETIME DEFAULT NULL COMMENT '打款时间',
`create_by` BIGINT DEFAULT NULL COMMENT '创建人',
`update_by` BIGINT DEFAULT NULL COMMENT '更新人',
`create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_xxk_distribution_withdraw_apply_no` (`withdraw_no`),
KEY `idx_xxk_distribution_withdraw_apply_member_user_id` (`member_user_id`),
KEY `idx_xxk_distribution_withdraw_apply_status` (`status`),
KEY `idx_xxk_distribution_withdraw_apply_submit_time` (`submit_time`),
KEY `idx_xxk_distribution_withdraw_apply_pay_time` (`pay_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='分销提现申请表';
@@ -0,0 +1,10 @@
-- 会员代理级别升级脚本
-- 1. 在会员资料表增加代理级别字段,默认二级代理。
-- 2. 回填历史空值,保证会员前台可显示代理级别和返佣比例。
ALTER TABLE `xxk_member_profile`
ADD COLUMN `agent_level` TINYINT NOT NULL DEFAULT 2 COMMENT '代理级别(1一级代理 2二级代理)' AFTER `invite_code`;
UPDATE `xxk_member_profile`
SET `agent_level` = 2
WHERE `agent_level` IS NULL;
@@ -0,0 +1,62 @@
-- 会员分销邀请码与独立佣金账户升级脚本
-- 说明:
-- 1. 为会员资料增加邀请码字段,支持邀请绑定。
-- 2. 为分销关系与佣金流水增加 member_user_id 口径,避免与后台 sys_user 混用。
-- 3. 新增独立佣金账户与账户流水,佣金可转入钱包,但不直接混入钱包口径。
ALTER TABLE `xxk_member_profile`
ADD COLUMN `invite_code` VARCHAR(32) NULL COMMENT '邀请码' AFTER `member_user_id`,
ADD UNIQUE INDEX `uk_xxk_member_profile_invite_code` (`invite_code`);
ALTER TABLE `xxk_distribution_relation`
ADD COLUMN `member_user_id` BIGINT NULL COMMENT '当前会员ID' AFTER `grand_parent_user_id`,
ADD COLUMN `parent_member_user_id` BIGINT NULL COMMENT '一级上级会员ID' AFTER `member_user_id`,
ADD COLUMN `grand_parent_member_user_id` BIGINT NULL COMMENT '二级上级会员ID' AFTER `parent_member_user_id`,
ADD UNIQUE INDEX `uk_xxk_distribution_relation_member_user_id` (`member_user_id`),
ADD KEY `idx_xxk_distribution_relation_parent_member_user_id` (`parent_member_user_id`);
ALTER TABLE `xxk_distribution_commission`
ADD COLUMN `member_user_id` BIGINT NULL COMMENT '佣金归属会员ID' AFTER `from_user_id`,
ADD COLUMN `from_member_user_id` BIGINT NULL COMMENT '消费会员ID' AFTER `member_user_id`,
ADD KEY `idx_xxk_distribution_commission_member_user_id` (`member_user_id`),
ADD KEY `idx_xxk_distribution_commission_from_member_user_id` (`from_member_user_id`);
CREATE TABLE `xxk_distribution_account` (
`id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`user_id` BIGINT DEFAULT NULL COMMENT '后台用户ID',
`member_user_id` BIGINT DEFAULT NULL COMMENT '会员ID',
`available_balance` DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '可用佣金余额',
`frozen_balance` DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '冻结佣金余额',
`total_earned_amount` DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '累计获得佣金',
`total_revoked_amount` DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '累计回退佣金',
`total_transfer_amount` DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '累计转入钱包金额',
`status` TINYINT NOT NULL DEFAULT 1 COMMENT '状态(1正常 0禁用)',
`create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_xxk_distribution_account_member_user_id` (`member_user_id`),
UNIQUE KEY `uk_xxk_distribution_account_user_id` (`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='分销佣金账户表';
CREATE TABLE `xxk_distribution_account_flow` (
`id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`flow_no` VARCHAR(64) NOT NULL COMMENT '流水号',
`user_id` BIGINT DEFAULT NULL COMMENT '后台用户ID',
`member_user_id` BIGINT DEFAULT NULL COMMENT '会员ID',
`commission_id` BIGINT DEFAULT NULL COMMENT '关联佣金记录ID',
`biz_type` VARCHAR(32) NOT NULL COMMENT '业务类型',
`change_type` VARCHAR(16) NOT NULL COMMENT '变动类型(IN/OUT)',
`change_amount` DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '变动金额',
`before_balance` DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '变动前余额',
`after_balance` DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '变动后余额',
`related_order_no` VARCHAR(64) DEFAULT NULL COMMENT '关联订单号',
`related_wallet_flow_no` VARCHAR(64) DEFAULT NULL COMMENT '关联钱包流水号',
`remark` VARCHAR(255) DEFAULT NULL COMMENT '备注',
`operate_by` BIGINT DEFAULT NULL COMMENT '操作人ID',
`create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_xxk_distribution_account_flow_no` (`flow_no`),
KEY `idx_xxk_distribution_account_flow_member_user_id` (`member_user_id`),
KEY `idx_xxk_distribution_account_flow_commission_id` (`commission_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='分销佣金账户流水表';
+88
View File
@@ -0,0 +1,88 @@
-- 会员体系初始化脚本
-- 执行说明:
-- 1. 本脚本只新增会员体系核心表,不影响现有后台管理员与代理业务表。
-- 2. 后续当订单、白名单、钱包正式切换到会员主账号时,再补业务迁移脚本。
CREATE TABLE IF NOT EXISTS `xxk_member_user` (
`id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`username` VARCHAR(64) NOT NULL COMMENT '用户名',
`nickname` VARCHAR(64) DEFAULT NULL COMMENT '昵称',
`password` VARCHAR(255) DEFAULT NULL COMMENT '密码',
`avatar` VARCHAR(255) DEFAULT NULL COMMENT '头像',
`mobile` VARCHAR(20) NOT NULL COMMENT '手机号',
`email` VARCHAR(128) DEFAULT NULL COMMENT '邮箱',
`status` TINYINT NOT NULL DEFAULT 1 COMMENT '状态(1正常 0禁用)',
`source` VARCHAR(32) NOT NULL DEFAULT 'MANUAL' COMMENT '注册来源',
`wx_openid` VARCHAR(64) DEFAULT NULL COMMENT '微信openid',
`wx_unionid` VARCHAR(64) DEFAULT NULL COMMENT '微信unionid',
`last_login_time` DATETIME DEFAULT NULL COMMENT '最后登录时间',
`last_login_ip` VARCHAR(64) DEFAULT NULL COMMENT '最后登录IP',
`remark` VARCHAR(255) DEFAULT NULL COMMENT '备注',
`create_by` BIGINT DEFAULT NULL COMMENT '创建人ID',
`update_by` BIGINT DEFAULT NULL COMMENT '更新人ID',
`create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`is_deleted` TINYINT NOT NULL DEFAULT 0 COMMENT '是否删除(0否 1是)',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_xxk_member_user_mobile` (`mobile`),
UNIQUE KEY `uk_xxk_member_user_username` (`username`),
KEY `idx_xxk_member_user_status` (`status`),
KEY `idx_xxk_member_user_source` (`source`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='会员主账号表';
CREATE TABLE IF NOT EXISTS `xxk_member_profile` (
`id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`member_user_id` BIGINT NOT NULL COMMENT '会员ID',
`agent_level` TINYINT NOT NULL DEFAULT 2 COMMENT '代理级别(1一级代理 2二级代理)',
`company_name` VARCHAR(128) DEFAULT NULL COMMENT '公司名称',
`contact_name` VARCHAR(64) DEFAULT NULL COMMENT '联系人',
`contact_wechat` VARCHAR(64) DEFAULT NULL COMMENT '联系微信',
`email` VARCHAR(128) DEFAULT NULL COMMENT '联系邮箱',
`qq` VARCHAR(32) DEFAULT NULL COMMENT 'QQ',
`address` VARCHAR(255) DEFAULT NULL COMMENT '地址',
`industry` VARCHAR(64) DEFAULT NULL COMMENT '行业',
`realname_status` TINYINT NOT NULL DEFAULT 0 COMMENT '实名状态(0未认证 1已认证)',
`remark` VARCHAR(255) DEFAULT NULL COMMENT '备注',
`create_by` BIGINT DEFAULT NULL COMMENT '创建人ID',
`update_by` BIGINT DEFAULT NULL COMMENT '更新人ID',
`create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`is_deleted` TINYINT NOT NULL DEFAULT 0 COMMENT '是否删除(0否 1是)',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_xxk_member_profile_user` (`member_user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='会员资料表';
CREATE TABLE IF NOT EXISTS `xxk_member_wallet` (
`id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`member_user_id` BIGINT NOT NULL COMMENT '会员ID',
`balance` DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '余额',
`frozen_balance` DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '冻结金额',
`total_recharge_amount` DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '累计充值金额',
`total_consume_amount` DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '累计消费金额',
`total_refund_amount` DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '累计退款金额',
`status` TINYINT NOT NULL DEFAULT 1 COMMENT '状态(1正常 0禁用)',
`create_by` BIGINT DEFAULT NULL COMMENT '创建人ID',
`update_by` BIGINT DEFAULT NULL COMMENT '更新人ID',
`create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`is_deleted` TINYINT NOT NULL DEFAULT 0 COMMENT '是否删除(0否 1是)',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_xxk_member_wallet_user` (`member_user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='会员钱包表';
CREATE TABLE IF NOT EXISTS `xxk_member_login_log` (
`id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`member_user_id` BIGINT DEFAULT NULL COMMENT '会员ID',
`mobile` VARCHAR(20) DEFAULT NULL COMMENT '登录手机号',
`login_type` VARCHAR(32) NOT NULL COMMENT '登录类型',
`login_ip` VARCHAR(64) DEFAULT NULL COMMENT '登录IP',
`login_region` VARCHAR(128) DEFAULT NULL COMMENT '登录地区',
`device_info` VARCHAR(255) DEFAULT NULL COMMENT '设备信息',
`status` TINYINT NOT NULL DEFAULT 1 COMMENT '登录结果(1成功 0失败)',
`message` VARCHAR(255) DEFAULT NULL COMMENT '结果说明',
`create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`),
KEY `idx_xxk_member_login_log_user` (`member_user_id`),
KEY `idx_xxk_member_login_log_mobile` (`mobile`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='会员登录日志表';
+54
View File
@@ -0,0 +1,54 @@
USE youlai_admin;
-- 会员管理菜单初始化
-- 说明:
-- 1. 本脚本负责会员后台管理菜单、按钮权限和系统管理员授权。
-- 2. 会员前台站点后续独立建设,此处仅覆盖管理后台会员运营入口。
-- 一级目录:会员中心
INSERT INTO `sys_menu`
(`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`,
`always_show`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`)
VALUES
(1300, 0, '0', '会员中心', 'C', 'Member', '/member', 'Layout', NULL,
1, NULL, 1, 11, 'user-filled', '/member/user', now(), now(), NULL)
ON DUPLICATE KEY UPDATE
`name` = VALUES(`name`),
`route_path` = VALUES(`route_path`),
`component` = VALUES(`component`),
`always_show` = VALUES(`always_show`),
`visible` = VALUES(`visible`),
`sort` = VALUES(`sort`),
`icon` = VALUES(`icon`),
`redirect` = VALUES(`redirect`),
`update_time` = now();
-- 会员管理
INSERT INTO `sys_menu`
(`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`,
`always_show`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`)
VALUES
(1310, 1300, '0,1300', '会员管理', 'M', 'MemberUser', 'user', 'member/user/index', NULL,
0, 1, 1, 1, 'avatar', NULL, now(), now(), NULL),
(13101, 1310, '0,1300,1310', '会员查询', 'B', NULL, '', NULL, 'member:user:list',
NULL, NULL, 1, 1, '', NULL, now(), now(), NULL),
(13102, 1310, '0,1300,1310', '会员新增', 'B', NULL, '', NULL, 'member:user:create',
NULL, NULL, 1, 2, '', NULL, now(), now(), NULL),
(13103, 1310, '0,1300,1310', '会员修改', 'B', NULL, '', NULL, 'member:user:update',
NULL, NULL, 1, 3, '', NULL, now(), now(), NULL)
ON DUPLICATE KEY UPDATE
`name` = VALUES(`name`),
`route_name` = VALUES(`route_name`),
`route_path` = VALUES(`route_path`),
`component` = VALUES(`component`),
`perm` = VALUES(`perm`),
`keep_alive` = VALUES(`keep_alive`),
`visible` = VALUES(`visible`),
`sort` = VALUES(`sort`),
`icon` = VALUES(`icon`),
`update_time` = now();
-- 系统管理员授权
INSERT IGNORE INTO `sys_role_menu` (`role_id`, `menu_id`) VALUES
(2, 1300),
(2, 1310), (2, 13101), (2, 13102), (2, 13103);
@@ -0,0 +1,41 @@
-- 会员代理业务 user_id 兼容升级脚本
-- 说明:
-- 1. 当前代理业务表最初按 sys_user 体系设计,user_id 为 NOT NULL。
-- 2. 会员体系接入后,会员侧业务主要依赖 member_user_id,很多写入场景不会再写 user_id。
-- 3. 因此需要将相关表的 user_id 调整为可空,避免会员新增、会员下单、钱包、静态/动态资源落库时报错。
ALTER TABLE `xxk_wallet_account`
MODIFY COLUMN `user_id` BIGINT NULL COMMENT '用户ID';
ALTER TABLE `xxk_wallet_flow`
MODIFY COLUMN `user_id` BIGINT NULL COMMENT '用户ID';
ALTER TABLE `xxk_proxy_order`
MODIFY COLUMN `user_id` BIGINT NULL COMMENT '用户ID';
ALTER TABLE `xxk_proxy_order_item`
MODIFY COLUMN `user_id` BIGINT NULL COMMENT '用户ID';
ALTER TABLE `xxk_static_proxy_asset`
MODIFY COLUMN `user_id` BIGINT NULL COMMENT '用户ID';
ALTER TABLE `xxk_static_proxy_whitelist`
MODIFY COLUMN `user_id` BIGINT NULL COMMENT '用户ID';
ALTER TABLE `xxk_dynamic_channel`
MODIFY COLUMN `user_id` BIGINT NULL COMMENT '用户ID';
ALTER TABLE `xxk_dynamic_channel_traffic_log`
MODIFY COLUMN `user_id` BIGINT NULL COMMENT '用户ID';
ALTER TABLE `xxk_dynamic_proxy_generate_log`
MODIFY COLUMN `user_id` BIGINT NULL COMMENT '用户ID';
-- 会员钱包账户应保持 member_user_id 唯一,避免同一会员生成多条钱包账户
ALTER TABLE `xxk_wallet_account`
DROP INDEX `idx_xxk_wallet_account_member_user_id`,
ADD UNIQUE INDEX `uk_xxk_wallet_account_member_user_id` (`member_user_id`);
-- 会员白名单也应按 member_user_id + whitelist_ip 去重
ALTER TABLE `xxk_static_proxy_whitelist`
ADD UNIQUE INDEX `uk_member_whitelist_ip` (`member_user_id`, `whitelist_ip`);
@@ -0,0 +1,49 @@
-- 会员代理业务归属升级脚本
-- 说明:
-- 1. 为核心代理业务表增加 member_user_id 字段,逐步从 sys_user 归属切到 member_user。
-- 2. 该脚本只做结构升级,不强制迁移历史数据。
ALTER TABLE `xxk_proxy_order`
ADD COLUMN `member_user_id` BIGINT NULL COMMENT '会员ID' AFTER `user_id`,
ADD KEY `idx_xxk_proxy_order_member_user_id` (`member_user_id`);
ALTER TABLE `xxk_proxy_order_item`
ADD COLUMN `member_user_id` BIGINT NULL COMMENT '会员ID' AFTER `user_id`,
ADD KEY `idx_xxk_proxy_order_item_member_user_id` (`member_user_id`);
ALTER TABLE `xxk_wallet_account`
ADD COLUMN `member_user_id` BIGINT NULL COMMENT '会员ID' AFTER `user_id`,
ADD KEY `idx_xxk_wallet_account_member_user_id` (`member_user_id`);
ALTER TABLE `xxk_wallet_flow`
ADD COLUMN `member_user_id` BIGINT NULL COMMENT '会员ID' AFTER `user_id`,
ADD KEY `idx_xxk_wallet_flow_member_user_id` (`member_user_id`);
ALTER TABLE `xxk_static_proxy_asset`
ADD COLUMN `member_user_id` BIGINT NULL COMMENT '会员ID' AFTER `user_id`,
ADD KEY `idx_xxk_static_proxy_asset_member_user_id` (`member_user_id`);
ALTER TABLE `xxk_static_proxy_whitelist`
ADD COLUMN `member_user_id` BIGINT NULL COMMENT '会员ID' AFTER `user_id`,
ADD KEY `idx_xxk_static_proxy_whitelist_member_user_id` (`member_user_id`);
ALTER TABLE `xxk_dynamic_channel`
ADD COLUMN `member_user_id` BIGINT NULL COMMENT '会员ID' AFTER `user_id`,
ADD KEY `idx_xxk_dynamic_channel_member_user_id` (`member_user_id`);
ALTER TABLE `xxk_dynamic_channel_traffic_log`
ADD COLUMN `member_user_id` BIGINT NULL COMMENT '会员ID' AFTER `user_id`,
ADD KEY `idx_xxk_dynamic_channel_traffic_log_member_user_id` (`member_user_id`);
ALTER TABLE `xxk_dynamic_proxy_generate_log`
ADD COLUMN `member_user_id` BIGINT NULL COMMENT '会员ID' AFTER `user_id`,
ADD KEY `idx_xxk_dynamic_proxy_generate_log_member_user_id` (`member_user_id`);
-- 历史数据迁移建议:
-- 可按手机号将 sys_user 关联到 xxk_member_user 后,回填 member_user_id。
-- 示例思路:
-- UPDATE xxk_wallet_account wa
-- JOIN sys_user su ON su.id = wa.user_id
-- JOIN xxk_member_user mu ON mu.mobile = su.mobile
-- SET wa.member_user_id = mu.id
-- WHERE wa.member_user_id IS NULL;
@@ -0,0 +1,61 @@
USE youlai_admin;
-- 会员认证菜单初始化
-- 说明:
-- 1. 在系统管理下新增“业务配置”统一入口,当前承载会员认证策略与会员登录注册配置。
-- 2. 在会员中心下新增“认证审核”页面,用于后台审核会员认证申请。
-- 系统管理 > 业务配置
INSERT INTO `sys_menu`
(`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`,
`always_show`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`)
VALUES
(271, 1, '0,1', '业务配置', 'M', 'BizConfigCenter', 'biz-config', 'system/biz-config/index', NULL,
0, 1, 1, 8, 'setting', NULL, NOW(), NOW(), NULL),
(2711, 271, '0,1,271', '会员认证策略查看', 'B', NULL, '', NULL, 'member:verify:policy:view',
NULL, NULL, 1, 1, '', NULL, NOW(), NOW(), NULL),
(2712, 271, '0,1,271', '会员认证策略修改', 'B', NULL, '', NULL, 'member:verify:policy:update',
NULL, NULL, 1, 2, '', NULL, NOW(), NOW(), NULL),
(2713, 271, '0,1,271', '会员登录注册配置查看', 'B', NULL, '', NULL, 'member:auth:config:view',
NULL, NULL, 1, 3, '', NULL, NOW(), NOW(), NULL),
(2714, 271, '0,1,271', '会员登录注册配置修改', 'B', NULL, '', NULL, 'member:auth:config:update',
NULL, NULL, 1, 4, '', NULL, NOW(), NOW(), NULL)
ON DUPLICATE KEY UPDATE
`name` = VALUES(`name`),
`route_name` = VALUES(`route_name`),
`route_path` = VALUES(`route_path`),
`component` = VALUES(`component`),
`perm` = VALUES(`perm`),
`keep_alive` = VALUES(`keep_alive`),
`visible` = VALUES(`visible`),
`sort` = VALUES(`sort`),
`icon` = VALUES(`icon`),
`update_time` = NOW();
-- 会员中心 > 认证审核
INSERT INTO `sys_menu`
(`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`,
`always_show`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`)
VALUES
(1320, 1300, '0,1300', '认证审核', 'M', 'MemberVerifyAudit', 'verify', 'member/verify/index', NULL,
0, 1, 1, 2, 'checked', NULL, NOW(), NOW(), NULL),
(13201, 1320, '0,1300,1320', '认证记录查询', 'B', NULL, '', NULL, 'member:verify:record:list',
NULL, NULL, 1, 1, '', NULL, NOW(), NOW(), NULL),
(13202, 1320, '0,1300,1320', '认证审核处理', 'B', NULL, '', NULL, 'member:verify:record:audit',
NULL, NULL, 1, 2, '', NULL, NOW(), NOW(), NULL)
ON DUPLICATE KEY UPDATE
`name` = VALUES(`name`),
`route_name` = VALUES(`route_name`),
`route_path` = VALUES(`route_path`),
`component` = VALUES(`component`),
`perm` = VALUES(`perm`),
`keep_alive` = VALUES(`keep_alive`),
`visible` = VALUES(`visible`),
`sort` = VALUES(`sort`),
`icon` = VALUES(`icon`),
`update_time` = NOW();
-- 系统管理员授权
INSERT IGNORE INTO `sys_role_menu` (`role_id`, `menu_id`) VALUES
(2, 271), (2, 2711), (2, 2712), (2, 2713), (2, 2714),
(2, 1320), (2, 13201), (2, 13202);
@@ -0,0 +1,68 @@
-- 会员认证配置与审核升级脚本
-- 说明:
-- 1. 扩展 sys_config 配置值长度,支持结构化 JSON 策略。
-- 2. 为会员资料表补充认证状态与最近一次审核信息。
-- 3. 新增会员认证记录表,保存提交快照和审核结果。
ALTER TABLE `sys_config`
MODIFY COLUMN `config_value` TEXT NOT NULL COMMENT '配置值';
ALTER TABLE `xxk_member_profile`
ADD COLUMN `real_name` VARCHAR(64) NULL COMMENT '真实姓名' AFTER `member_user_id`,
ADD COLUMN `id_card_no` VARCHAR(32) NULL COMMENT '身份证号' AFTER `real_name`,
ADD COLUMN `id_card_front_url` VARCHAR(255) NULL COMMENT '身份证人像面' AFTER `id_card_no`,
ADD COLUMN `id_card_back_url` VARCHAR(255) NULL COMMENT '身份证国徽面' AFTER `id_card_front_url`,
ADD COLUMN `support_docs_json` TEXT NULL COMMENT '补充材料JSON' AFTER `id_card_back_url`,
MODIFY COLUMN `realname_status` TINYINT NOT NULL DEFAULT 0 COMMENT '实名状态(0未提交 1待审核 2已认证 3已驳回)',
ADD COLUMN `last_verify_record_id` BIGINT NULL COMMENT '最近认证记录ID' AFTER `realname_status`,
ADD COLUMN `last_submit_time` DATETIME NULL COMMENT '最近提交时间' AFTER `last_verify_record_id`,
ADD COLUMN `verified_time` DATETIME NULL COMMENT '认证通过时间' AFTER `last_submit_time`,
ADD COLUMN `audit_time` DATETIME NULL COMMENT '审核时间' AFTER `verified_time`,
ADD COLUMN `audit_by` BIGINT NULL COMMENT '审核人ID' AFTER `audit_time`,
ADD COLUMN `audit_remark` VARCHAR(255) NULL COMMENT '审核备注' AFTER `audit_by`;
CREATE TABLE `xxk_member_verify_record` (
`id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`verify_no` VARCHAR(64) NOT NULL COMMENT '认证单号',
`member_user_id` BIGINT NOT NULL COMMENT '会员ID',
`username_snapshot` VARCHAR(64) DEFAULT NULL COMMENT '用户名快照',
`nickname_snapshot` VARCHAR(64) DEFAULT NULL COMMENT '昵称快照',
`mobile_snapshot` VARCHAR(20) DEFAULT NULL COMMENT '手机号快照',
`real_name` VARCHAR(64) DEFAULT NULL COMMENT '真实姓名',
`id_card_no` VARCHAR(32) DEFAULT NULL COMMENT '身份证号',
`id_card_front_url` VARCHAR(255) DEFAULT NULL COMMENT '身份证人像面',
`id_card_back_url` VARCHAR(255) DEFAULT NULL COMMENT '身份证国徽面',
`support_docs_json` TEXT DEFAULT NULL COMMENT '补充材料JSON',
`policy_snapshot_json` LONGTEXT DEFAULT NULL COMMENT '策略快照JSON',
`form_data_json` LONGTEXT DEFAULT NULL COMMENT '提交数据JSON',
`status` TINYINT NOT NULL DEFAULT 1 COMMENT '状态(1待审核 2通过 3驳回)',
`submit_remark` VARCHAR(255) DEFAULT NULL COMMENT '提交备注',
`submit_time` DATETIME DEFAULT NULL COMMENT '提交时间',
`audit_time` DATETIME DEFAULT NULL COMMENT '审核时间',
`audit_by` BIGINT DEFAULT NULL COMMENT '审核人ID',
`audit_remark` VARCHAR(255) DEFAULT NULL COMMENT '审核备注',
`create_by` BIGINT DEFAULT NULL COMMENT '创建人ID',
`update_by` BIGINT DEFAULT NULL COMMENT '更新人ID',
`create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`is_deleted` TINYINT NOT NULL DEFAULT 0 COMMENT '是否删除(0否 1是)',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_xxk_member_verify_record_no` (`verify_no`),
KEY `idx_xxk_member_verify_record_member_user_id` (`member_user_id`),
KEY `idx_xxk_member_verify_record_status` (`status`),
KEY `idx_xxk_member_verify_record_submit_time` (`submit_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='会员认证记录表';
INSERT INTO `sys_config` (`config_name`, `config_key`, `config_value`, `remark`, `create_time`, `create_by`, `is_deleted`)
SELECT
'会员认证策略',
'member.verify.policy',
'{"enabled":true,"forceRequired":false,"forceScenes":[],"fields":[{"code":"mobile","label":"手机号","type":"TEXT","enabled":true,"required":true,"readonly":true,"source":"ACCOUNT","maxCount":1,"sort":10,"placeholder":"自动读取账号手机号"},{"code":"realName","label":"姓名","type":"TEXT","enabled":true,"required":true,"readonly":false,"source":"USER_INPUT","maxCount":1,"sort":20,"placeholder":"请输入真实姓名"},{"code":"idCardNo","label":"身份证号","type":"TEXT","enabled":true,"required":true,"readonly":false,"source":"USER_INPUT","maxCount":1,"sort":30,"placeholder":"请输入身份证号"},{"code":"idCardFrontUrl","label":"身份证人像面","type":"IMAGE","enabled":true,"required":true,"readonly":false,"source":"USER_INPUT","maxCount":1,"sort":40,"placeholder":"请上传身份证人像面"},{"code":"idCardBackUrl","label":"身份证国徽面","type":"IMAGE","enabled":true,"required":true,"readonly":false,"source":"USER_INPUT","maxCount":1,"sort":50,"placeholder":"请上传身份证国徽面"},{"code":"supportDocs","label":"其他图片辅证","type":"IMAGE_LIST","enabled":true,"required":false,"readonly":false,"source":"USER_INPUT","maxCount":5,"sort":60,"placeholder":"可上传补充图片材料"}]}',
'会员实名认证表单与强制校验策略',
NOW(),
1,
0
FROM DUAL
WHERE NOT EXISTS (
SELECT 1 FROM `sys_config` WHERE `config_key` = 'member.verify.policy'
);
@@ -0,0 +1,69 @@
-- API 独立账户初始化脚本
CREATE TABLE IF NOT EXISTS `xxk_open_api_account` (
`id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`open_api_app_id` BIGINT NOT NULL COMMENT '开放应用ID',
`member_user_id` BIGINT NOT NULL COMMENT '会员ID',
`balance` DECIMAL(18,2) NOT NULL DEFAULT 0 COMMENT '余额',
`frozen_balance` DECIMAL(18,2) NOT NULL DEFAULT 0 COMMENT '冻结余额',
`total_recharge_amount` DECIMAL(18,2) NOT NULL DEFAULT 0 COMMENT '累计充值金额',
`total_consume_amount` DECIMAL(18,2) NOT NULL DEFAULT 0 COMMENT '累计消费金额',
`total_refund_amount` DECIMAL(18,2) NOT NULL DEFAULT 0 COMMENT '累计退款金额',
`status` TINYINT NOT NULL DEFAULT 1 COMMENT '状态(0停用 1正常)',
`create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_xxk_open_api_account_app_id` (`open_api_app_id`),
KEY `idx_xxk_open_api_account_member_user_id` (`member_user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='API独立账户表';
CREATE TABLE IF NOT EXISTS `xxk_open_api_account_flow` (
`id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`flow_no` VARCHAR(64) NOT NULL COMMENT '流水号',
`open_api_app_id` BIGINT NOT NULL COMMENT '开放应用ID',
`member_user_id` BIGINT NOT NULL COMMENT '会员ID',
`biz_type` VARCHAR(64) NOT NULL COMMENT '业务类型',
`change_type` VARCHAR(16) NOT NULL COMMENT '变动类型(IN/OUT)',
`change_amount` DECIMAL(18,2) NOT NULL DEFAULT 0 COMMENT '变动金额',
`before_balance` DECIMAL(18,2) NOT NULL DEFAULT 0 COMMENT '变动前余额',
`after_balance` DECIMAL(18,2) NOT NULL DEFAULT 0 COMMENT '变动后余额',
`related_order_no` VARCHAR(64) DEFAULT NULL COMMENT '关联单号',
`remark` VARCHAR(255) DEFAULT NULL COMMENT '备注',
`operate_by` BIGINT DEFAULT NULL COMMENT '操作人ID',
`create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_xxk_open_api_account_flow_no` (`flow_no`),
KEY `idx_xxk_open_api_account_flow_app_id` (`open_api_app_id`),
KEY `idx_xxk_open_api_account_flow_related_order_no` (`related_order_no`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='API独立账户流水表';
CREATE TABLE IF NOT EXISTS `xxk_open_api_recharge_order` (
`id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`recharge_no` VARCHAR(64) NOT NULL COMMENT '充值单号',
`pay_order_no` VARCHAR(64) NOT NULL COMMENT '支付单号',
`open_api_app_id` BIGINT NOT NULL COMMENT '开放应用ID',
`member_user_id` BIGINT NOT NULL COMMENT '会员ID',
`payment_type` VARCHAR(32) NOT NULL COMMENT '支付方式',
`pay_status` VARCHAR(32) NOT NULL COMMENT '支付状态',
`channel_order_no` VARCHAR(64) DEFAULT NULL COMMENT '渠道订单号',
`channel_response` LONGTEXT DEFAULT NULL COMMENT '渠道响应',
`client_type` VARCHAR(32) DEFAULT NULL COMMENT '客户端类型',
`return_url` VARCHAR(500) DEFAULT NULL COMMENT '回跳地址',
`amount` DECIMAL(18,2) NOT NULL DEFAULT 0 COMMENT '充值金额',
`gift_amount` DECIMAL(18,2) NOT NULL DEFAULT 0 COMMENT '赠送金额',
`credited_amount` DECIMAL(18,2) NOT NULL DEFAULT 0 COMMENT '到账金额',
`promotion_rule_snapshot` TEXT DEFAULT NULL COMMENT '活动规则快照',
`currency` VARCHAR(16) DEFAULT 'USD' COMMENT '币种',
`paid_time` DATETIME DEFAULT NULL COMMENT '支付完成时间',
`remark` VARCHAR(255) DEFAULT NULL COMMENT '备注',
`is_deleted` TINYINT NOT NULL DEFAULT 0 COMMENT '是否删除(0否 1是)',
`create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_xxk_open_api_recharge_no` (`recharge_no`),
UNIQUE KEY `uk_xxk_open_api_pay_order_no` (`pay_order_no`),
KEY `idx_xxk_open_api_recharge_order_app_id` (`open_api_app_id`),
KEY `idx_xxk_open_api_recharge_order_member_user_id` (`member_user_id`),
KEY `idx_xxk_open_api_recharge_order_pay_status` (`pay_status`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='API独立账户充值单表';
+25
View File
@@ -0,0 +1,25 @@
-- 开放接口应用初始化脚本
-- 当前版本采用 appId + appSecret 换 accessToken 的轻量模式。
CREATE TABLE IF NOT EXISTS `xxk_open_api_app` (
`id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`app_id` VARCHAR(64) NOT NULL COMMENT '应用ID',
`app_name` VARCHAR(128) NOT NULL COMMENT '应用名称',
`app_secret` VARCHAR(128) NOT NULL COMMENT '应用密钥',
`member_user_id` BIGINT NOT NULL COMMENT '绑定会员ID',
`status` TINYINT NOT NULL DEFAULT 1 COMMENT '状态(0停用 1启用)',
`allow_ip_list` TEXT DEFAULT NULL COMMENT '允许访问IP列表,逗号/换行分隔',
`last_auth_time` DATETIME DEFAULT NULL COMMENT '最近换取token时间',
`last_auth_ip` VARCHAR(64) DEFAULT NULL COMMENT '最近换取token IP',
`remark` VARCHAR(255) DEFAULT NULL COMMENT '备注',
`create_by` BIGINT DEFAULT NULL COMMENT '创建人ID',
`update_by` BIGINT DEFAULT NULL COMMENT '更新人ID',
`create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`is_deleted` TINYINT NOT NULL DEFAULT 0 COMMENT '是否删除(0否 1是)',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_xxk_open_api_app_app_id` (`app_id`),
UNIQUE KEY `uk_xxk_open_api_app_member_user_id` (`member_user_id`),
KEY `idx_xxk_open_api_app_member_user_id` (`member_user_id`),
KEY `idx_xxk_open_api_app_status` (`status`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='开放接口应用表';
@@ -0,0 +1,33 @@
-- 开放接口申请与审核初始化脚本
CREATE TABLE IF NOT EXISTS `xxk_open_api_apply` (
`id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`apply_no` VARCHAR(64) NOT NULL COMMENT '申请单号',
`member_user_id` BIGINT NOT NULL COMMENT '会员ID',
`username_snapshot` VARCHAR(64) DEFAULT NULL COMMENT '用户名快照',
`mobile_snapshot` VARCHAR(32) DEFAULT NULL COMMENT '手机号快照',
`contact_name` VARCHAR(64) NOT NULL COMMENT '联系人',
`contact_mobile` VARCHAR(32) DEFAULT NULL COMMENT '联系电话',
`contact_email` VARCHAR(128) DEFAULT NULL COMMENT '联系邮箱',
`company_name` VARCHAR(128) DEFAULT NULL COMMENT '公司名称',
`purpose` VARCHAR(255) NOT NULL COMMENT '申请用途',
`scenario_description` TEXT NOT NULL COMMENT '使用场景说明',
`allow_ip_list` TEXT DEFAULT NULL COMMENT 'IP白名单',
`status` TINYINT NOT NULL DEFAULT 0 COMMENT '状态(0待审核 1已通过 2已驳回)',
`submit_remark` VARCHAR(255) DEFAULT NULL COMMENT '补充说明',
`submit_time` DATETIME DEFAULT NULL COMMENT '提交时间',
`audit_time` DATETIME DEFAULT NULL COMMENT '审核时间',
`audit_by` BIGINT DEFAULT NULL COMMENT '审核人ID',
`audit_remark` VARCHAR(255) DEFAULT NULL COMMENT '审核备注',
`open_api_app_id` BIGINT DEFAULT NULL COMMENT '关联开放应用ID',
`create_by` BIGINT DEFAULT NULL COMMENT '创建人ID',
`update_by` BIGINT DEFAULT NULL COMMENT '更新人ID',
`create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`is_deleted` TINYINT NOT NULL DEFAULT 0 COMMENT '是否删除(0否 1是)',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_xxk_open_api_apply_no` (`apply_no`),
KEY `idx_xxk_open_api_apply_member_user_id` (`member_user_id`),
KEY `idx_xxk_open_api_apply_status` (`status`),
KEY `idx_xxk_open_api_apply_submit_time` (`submit_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='开放接口申请表';
@@ -0,0 +1,59 @@
-- 开放API回调能力升级
SET @schema_name = DATABASE();
SET @column_exists = (
SELECT COUNT(1)
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_SCHEMA = @schema_name
AND TABLE_NAME = 'xxk_open_api_app'
AND COLUMN_NAME = 'callback_url'
);
SET @sql = IF(
@column_exists = 0,
'ALTER TABLE `xxk_open_api_app` ADD COLUMN `callback_url` VARCHAR(255) DEFAULT NULL COMMENT ''订单结果回调地址'' AFTER `allow_ip_list`',
'SELECT 1'
);
PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
SET @column_exists = (
SELECT COUNT(1)
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_SCHEMA = @schema_name
AND TABLE_NAME = 'xxk_open_api_app'
AND COLUMN_NAME = 'callback_secret'
);
SET @sql = IF(
@column_exists = 0,
'ALTER TABLE `xxk_open_api_app` ADD COLUMN `callback_secret` VARCHAR(128) DEFAULT NULL COMMENT ''订单结果回调签名密钥'' AFTER `callback_url`',
'SELECT 1'
);
PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
CREATE TABLE IF NOT EXISTS `xxk_open_api_callback_log` (
`id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`open_api_app_id` BIGINT NOT NULL COMMENT '开放应用ID',
`member_user_id` BIGINT NOT NULL COMMENT '会员ID',
`order_id` BIGINT NOT NULL COMMENT '订单ID',
`order_no` VARCHAR(64) NOT NULL COMMENT '订单号',
`callback_url` VARCHAR(255) DEFAULT NULL COMMENT '回调地址',
`event_type` VARCHAR(64) NOT NULL COMMENT '事件类型',
`request_body` LONGTEXT DEFAULT NULL COMMENT '请求报文',
`response_body` LONGTEXT DEFAULT NULL COMMENT '响应报文',
`response_status` INT DEFAULT NULL COMMENT '响应HTTP状态码',
`delivery_status` VARCHAR(32) NOT NULL DEFAULT 'PENDING' COMMENT '投递状态(PENDING/SUCCESS/FAIL/SKIPPED)',
`error_message` VARCHAR(500) DEFAULT NULL COMMENT '错误信息',
`attempt_no` INT NOT NULL DEFAULT 1 COMMENT '尝试次数',
`trigger_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '触发时间',
`finish_time` DATETIME DEFAULT NULL COMMENT '完成时间',
`create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`),
KEY `idx_xxk_open_api_callback_log_app_id` (`open_api_app_id`),
KEY `idx_xxk_open_api_callback_log_order_no` (`order_no`),
KEY `idx_xxk_open_api_callback_log_status` (`delivery_status`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='开放API订单回调日志表';
+41
View File
@@ -0,0 +1,41 @@
USE youlai_admin;
-- 开放 API 审核菜单初始化
-- 说明:
-- 1. 在“代理平台”下新增“开放API审核”菜单。
-- 2. 用于审核会员开放 API 申请,并授予管理员默认访问权限。
INSERT INTO `sys_menu`
(`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`,
`always_show`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`)
VALUES
(1220, 1100, '0,1100', '开放API审核', 'C', NULL, '/open-api-manage', 'Layout', NULL,
1, 0, 1, 13, 'api', '/open-api-manage/open-api', NOW(), NOW(), NULL),
(1221, 1220, '0,1100,1220', '开放API申请审核', 'M', 'ProxyOpenApiApply', 'open-api', 'proxy/open-api/index', NULL,
0, 1, 1, 1, 'form', NULL, NOW(), NOW(), NULL),
(122101, 1221, '0,1100,1220,1221', '开放API申请查询', 'B', NULL, '', NULL, 'openapi:apply:list',
NULL, NULL, 1, 1, '', NULL, NOW(), NOW(), NULL),
(122102, 1221, '0,1100,1220,1221', '开放API申请审核', 'B', NULL, '', NULL, 'openapi:apply:audit',
NULL, NULL, 1, 2, '', NULL, NOW(), NOW(), NULL),
(1222, 1220, '0,1100,1220', '开放API回调日志', 'M', 'ProxyOpenApiCallback', 'open-api-callback', 'proxy/open-api-callback/index', NULL,
0, 1, 1, 2, 'notification', NULL, NOW(), NOW(), NULL),
(122201, 1222, '0,1100,1220,1222', '开放API回调日志查询', 'B', NULL, '', NULL, 'openapi:callback-log:list',
NULL, NULL, 1, 1, '', NULL, NOW(), NOW(), NULL),
(122202, 1222, '0,1100,1220,1222', '开放API回调日志重发', 'B', NULL, '', NULL, 'openapi:callback-log:retry',
NULL, NULL, 1, 2, '', NULL, NOW(), NOW(), NULL),
(1223, 1220, '0,1100,1220', '开放API账户中心', 'M', 'ProxyOpenApiAccount', 'open-api-account', 'proxy/open-api-account/index', NULL,
0, 1, 0, 3, 'wallet', NULL, NOW(), NOW(), NULL)
ON DUPLICATE KEY UPDATE
`name` = VALUES(`name`),
`route_name` = VALUES(`route_name`),
`route_path` = VALUES(`route_path`),
`component` = VALUES(`component`),
`perm` = VALUES(`perm`),
`keep_alive` = VALUES(`keep_alive`),
`visible` = VALUES(`visible`),
`sort` = VALUES(`sort`),
`icon` = VALUES(`icon`),
`update_time` = NOW();
INSERT IGNORE INTO `sys_role_menu` (`role_id`, `menu_id`) VALUES
(2, 1220), (2, 1221), (2, 122101), (2, 122102), (2, 1222), (2, 122201), (2, 122202), (2, 1223);
@@ -0,0 +1,5 @@
ALTER TABLE `xxk_product_static_country_price`
DROP INDEX `uk_product_country`,
ADD UNIQUE KEY `uk_product_static_price_region` (`product_id`, `region_id`);
@@ -0,0 +1,17 @@
USE youlai_admin;
-- 分销配置演示数据
-- 说明:
-- 1. 仅当当前不存在启用中的分销配置时插入。
-- 2. 佣金比例示例:一级 10%,二级 3%。
INSERT INTO `xxk_distribution_config`
(`distribution_enabled`, `first_level_rate`, `second_level_rate`, `withdraw_threshold`, `withdraw_fee_rate`, `settle_rule`, `status`, `remark`, `create_by`, `create_time`, `update_by`, `update_time`)
SELECT 1, 0.1000, 0.0300, 0.00, 0.0000, 'OPEN_SUCCESS_AMOUNT', 1, '代理平台演示分销配置', 1, now(), 1, now()
FROM DUAL
WHERE NOT EXISTS (
SELECT 1
FROM `xxk_distribution_config`
WHERE `distribution_enabled` = 1
AND `status` = 1
);
+292
View File
@@ -0,0 +1,292 @@
USE youlai_admin;
-- 代理平台菜单初始化
-- 说明:
-- 1. 本脚本只负责后台菜单、按钮权限和系统管理员授权。
-- 2. 页面 component 先按约定路径预留,前端页面接入后直接对齐即可。
-- 一级目录:代理平台
INSERT INTO `sys_menu`
(`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`,
`always_show`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`)
VALUES
(1100, 0, '0', '代理平台', 'C', 'Proxy', '/proxy', 'Layout', NULL,
1, NULL, 1, 10, 'monitor', '/proxy/product', now(), now(), NULL)
ON DUPLICATE KEY UPDATE
`name` = VALUES(`name`),
`route_path` = VALUES(`route_path`),
`component` = VALUES(`component`),
`always_show` = VALUES(`always_show`),
`visible` = VALUES(`visible`),
`sort` = VALUES(`sort`),
`icon` = VALUES(`icon`),
`redirect` = VALUES(`redirect`),
`update_time` = now();
-- 商品管理
INSERT INTO `sys_menu`
(`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`,
`always_show`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`)
VALUES
(1110, 1100, '0,1100', '商品管理', 'M', 'ProxyProduct', 'product', 'proxy/product/index', NULL,
0, 1, 1, 1, 'goods', NULL, now(), now(), NULL),
(11101, 1110, '0,1100,1110', '商品查询', 'B', NULL, '', NULL, 'proxy:product:list',
NULL, NULL, 1, 1, '', NULL, now(), now(), NULL),
(11102, 1110, '0,1100,1110', '商品新增', 'B', NULL, '', NULL, 'proxy:product:create',
NULL, NULL, 1, 2, '', NULL, now(), now(), NULL),
(11103, 1110, '0,1100,1110', '商品修改', 'B', NULL, '', NULL, 'proxy:product:update',
NULL, NULL, 1, 3, '', NULL, now(), now(), NULL),
(11104, 1110, '0,1100,1110', '商品删除', 'B', NULL, '', NULL, 'proxy:product:delete',
NULL, NULL, 1, 4, '', NULL, now(), now(), NULL)
ON DUPLICATE KEY UPDATE
`name` = VALUES(`name`),
`route_name` = VALUES(`route_name`),
`route_path` = VALUES(`route_path`),
`component` = VALUES(`component`),
`perm` = VALUES(`perm`),
`keep_alive` = VALUES(`keep_alive`),
`visible` = VALUES(`visible`),
`sort` = VALUES(`sort`),
`update_time` = now();
-- 静态价格管理
INSERT INTO `sys_menu`
(`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`,
`always_show`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`)
VALUES
(1120, 1100, '0,1100', '静态价格管理', 'M', 'ProxyStaticPrice', 'static-price', 'proxy/static-price/index', NULL,
0, 1, 1, 2, 'price-tag', NULL, now(), now(), NULL),
(11201, 1120, '0,1100,1120', '静态价格查询', 'B', NULL, '', NULL, 'proxy:static-price:list',
NULL, NULL, 1, 1, '', NULL, now(), now(), NULL),
(11202, 1120, '0,1100,1120', '静态价格新增', 'B', NULL, '', NULL, 'proxy:static-price:create',
NULL, NULL, 1, 2, '', NULL, now(), now(), NULL),
(11203, 1120, '0,1100,1120', '静态价格修改', 'B', NULL, '', NULL, 'proxy:static-price:update',
NULL, NULL, 1, 3, '', NULL, now(), now(), NULL),
(11204, 1120, '0,1100,1120', '静态价格删除', 'B', NULL, '', NULL, 'proxy:static-price:delete',
NULL, NULL, 1, 4, '', NULL, now(), now(), NULL)
ON DUPLICATE KEY UPDATE
`name` = VALUES(`name`),
`route_name` = VALUES(`route_name`),
`route_path` = VALUES(`route_path`),
`component` = VALUES(`component`),
`perm` = VALUES(`perm`),
`keep_alive` = VALUES(`keep_alive`),
`visible` = VALUES(`visible`),
`sort` = VALUES(`sort`),
`update_time` = now();
-- 动态套餐管理
INSERT INTO `sys_menu`
(`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`,
`always_show`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`)
VALUES
(1130, 1100, '0,1100', '动态套餐管理', 'M', 'ProxyDynamicPlan', 'dynamic-plan', 'proxy/dynamic-plan/index', NULL,
0, 1, 1, 3, 'set-up', NULL, now(), now(), NULL),
(11301, 1130, '0,1100,1130', '动态套餐查询', 'B', NULL, '', NULL, 'proxy:dynamic-plan:list',
NULL, NULL, 1, 1, '', NULL, now(), now(), NULL),
(11302, 1130, '0,1100,1130', '动态套餐新增', 'B', NULL, '', NULL, 'proxy:dynamic-plan:create',
NULL, NULL, 1, 2, '', NULL, now(), now(), NULL),
(11303, 1130, '0,1100,1130', '动态套餐修改', 'B', NULL, '', NULL, 'proxy:dynamic-plan:update',
NULL, NULL, 1, 3, '', NULL, now(), now(), NULL),
(11304, 1130, '0,1100,1130', '动态套餐删除', 'B', NULL, '', NULL, 'proxy:dynamic-plan:delete',
NULL, NULL, 1, 4, '', NULL, now(), now(), NULL)
ON DUPLICATE KEY UPDATE
`name` = VALUES(`name`),
`route_name` = VALUES(`route_name`),
`route_path` = VALUES(`route_path`),
`component` = VALUES(`component`),
`perm` = VALUES(`perm`),
`keep_alive` = VALUES(`keep_alive`),
`visible` = VALUES(`visible`),
`sort` = VALUES(`sort`),
`update_time` = now();
-- 时长倍率管理
INSERT INTO `sys_menu`
(`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`,
`always_show`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`)
VALUES
(1140, 1100, '0,1100', '时长倍率管理', 'M', 'ProxyDurationMultiplier', 'duration-multiplier', 'proxy/duration-multiplier/index', NULL,
0, 1, 1, 4, 'timer', NULL, now(), now(), NULL),
(11401, 1140, '0,1100,1140', '时长倍率查询', 'B', NULL, '', NULL, 'proxy:duration:list',
NULL, NULL, 1, 1, '', NULL, now(), now(), NULL),
(11402, 1140, '0,1100,1140', '时长倍率新增', 'B', NULL, '', NULL, 'proxy:duration:create',
NULL, NULL, 1, 2, '', NULL, now(), now(), NULL),
(11403, 1140, '0,1100,1140', '时长倍率修改', 'B', NULL, '', NULL, 'proxy:duration:update',
NULL, NULL, 1, 3, '', NULL, now(), now(), NULL),
(11404, 1140, '0,1100,1140', '时长倍率删除', 'B', NULL, '', NULL, 'proxy:duration:delete',
NULL, NULL, 1, 4, '', NULL, now(), now(), NULL)
ON DUPLICATE KEY UPDATE
`name` = VALUES(`name`),
`route_name` = VALUES(`route_name`),
`route_path` = VALUES(`route_path`),
`component` = VALUES(`component`),
`perm` = VALUES(`perm`),
`keep_alive` = VALUES(`keep_alive`),
`visible` = VALUES(`visible`),
`sort` = VALUES(`sort`),
`update_time` = now();
-- 订单管理
INSERT INTO `sys_menu`
(`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`,
`always_show`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`)
VALUES
(1150, 1100, '0,1100', '订单管理', 'M', 'ProxyOrder', 'order', 'proxy/order/index', NULL,
0, 1, 1, 5, 'tickets', NULL, now(), now(), NULL),
(11501, 1150, '0,1100,1150', '订单查询', 'B', NULL, '', NULL, 'proxy:order:list',
NULL, NULL, 1, 1, '', NULL, now(), now(), NULL),
(11502, 1150, '0,1100,1150', '订单创建', 'B', NULL, '', NULL, 'proxy:order:create',
NULL, NULL, 1, 2, '', NULL, now(), now(), NULL),
(11503, 1150, '0,1100,1150', '订单支付', 'B', NULL, '', NULL, 'proxy:order:pay',
NULL, NULL, 1, 3, '', NULL, now(), now(), NULL),
(11504, 1150, '0,1100,1150', '订单补偿重试', 'B', NULL, '', NULL, 'proxy:order:compensate',
NULL, NULL, 1, 4, '', NULL, now(), now(), NULL),
(11505, 1150, '0,1100,1150', '订单取消', 'B', NULL, '', NULL, 'proxy:order:cancel',
NULL, NULL, 1, 5, '', NULL, now(), now(), NULL)
ON DUPLICATE KEY UPDATE
`name` = VALUES(`name`),
`route_name` = VALUES(`route_name`),
`route_path` = VALUES(`route_path`),
`component` = VALUES(`component`),
`perm` = VALUES(`perm`),
`keep_alive` = VALUES(`keep_alive`),
`visible` = VALUES(`visible`),
`sort` = VALUES(`sort`),
`update_time` = now();
-- 静态代理管理
INSERT INTO `sys_menu`
(`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`,
`always_show`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`)
VALUES
(1160, 1100, '0,1100', '静态代理管理', 'M', 'ProxyStaticAsset', 'static-asset', 'proxy/static-asset/index', NULL,
0, 1, 1, 6, 'connection', NULL, now(), now(), NULL),
(11601, 1160, '0,1100,1160', '静态代理查询', 'B', NULL, '', NULL, 'proxy:static-asset:list',
NULL, NULL, 1, 1, '', NULL, now(), now(), NULL),
(11602, 1160, '0,1100,1160', '静态代理续费', 'B', NULL, '', NULL, 'proxy:static-asset:renew',
NULL, NULL, 1, 2, '', NULL, now(), now(), NULL),
(11603, 1160, '0,1100,1160', '静态代理维护', 'B', NULL, '', NULL, 'proxy:static-asset:update',
NULL, NULL, 1, 3, '', NULL, now(), now(), NULL)
ON DUPLICATE KEY UPDATE
`name` = VALUES(`name`),
`route_name` = VALUES(`route_name`),
`route_path` = VALUES(`route_path`),
`component` = VALUES(`component`),
`perm` = VALUES(`perm`),
`keep_alive` = VALUES(`keep_alive`),
`visible` = VALUES(`visible`),
`sort` = VALUES(`sort`),
`update_time` = now();
-- 动态通道管理
INSERT INTO `sys_menu`
(`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`,
`always_show`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`)
VALUES
(1170, 1100, '0,1100', '动态通道管理', 'M', 'ProxyDynamicChannel', 'dynamic-channel', 'proxy/dynamic-channel/index', NULL,
0, 1, 1, 7, 'share', NULL, now(), now(), NULL),
(11701, 1170, '0,1100,1170', '动态通道查询', 'B', NULL, '', NULL, 'proxy:dynamic-channel:list',
NULL, NULL, 1, 1, '', NULL, now(), now(), NULL),
(11702, 1170, '0,1100,1170', '动态通道维护', 'B', NULL, '', NULL, 'proxy:dynamic-channel:update',
NULL, NULL, 1, 2, '', NULL, now(), now(), NULL)
ON DUPLICATE KEY UPDATE
`name` = VALUES(`name`),
`route_name` = VALUES(`route_name`),
`route_path` = VALUES(`route_path`),
`component` = VALUES(`component`),
`perm` = VALUES(`perm`),
`keep_alive` = VALUES(`keep_alive`),
`visible` = VALUES(`visible`),
`sort` = VALUES(`sort`),
`update_time` = now();
-- 上游供应商管理
INSERT INTO `sys_menu`
(`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`,
`always_show`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`)
VALUES
(1180, 1100, '0,1100', '上游供应商管理', 'M', 'ProxyUpstreamProvider', 'upstream-provider', 'proxy/upstream-provider/index', NULL,
0, 1, 1, 8, 'cpu', NULL, now(), now(), NULL),
(11801, 1180, '0,1100,1180', '上游供应商查询', 'B', NULL, '', NULL, 'proxy:upstream:list',
NULL, NULL, 1, 1, '', NULL, now(), now(), NULL),
(11802, 1180, '0,1100,1180', '上游供应商新增', 'B', NULL, '', NULL, 'proxy:upstream:create',
NULL, NULL, 1, 2, '', NULL, now(), now(), NULL),
(11803, 1180, '0,1100,1180', '上游供应商修改', 'B', NULL, '', NULL, 'proxy:upstream:update',
NULL, NULL, 1, 3, '', NULL, now(), now(), NULL),
(11804, 1180, '0,1100,1180', '上游供应商删除', 'B', NULL, '', NULL, 'proxy:upstream:delete',
NULL, NULL, 1, 4, '', NULL, now(), now(), NULL)
ON DUPLICATE KEY UPDATE
`name` = VALUES(`name`),
`route_name` = VALUES(`route_name`),
`route_path` = VALUES(`route_path`),
`component` = VALUES(`component`),
`perm` = VALUES(`perm`),
`keep_alive` = VALUES(`keep_alive`),
`visible` = VALUES(`visible`),
`sort` = VALUES(`sort`),
`update_time` = now();
-- 钱包管理
INSERT INTO `sys_menu`
(`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`,
`always_show`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`)
VALUES
(1190, 1100, '0,1100', '钱包管理', 'M', 'ProxyWallet', 'wallet', 'proxy/wallet/index', NULL,
0, 1, 1, 9, 'wallet', NULL, now(), now(), NULL),
(11901, 1190, '0,1100,1190', '钱包查询', 'B', NULL, '', NULL, 'proxy:wallet:list',
NULL, NULL, 1, 1, '', NULL, now(), now(), NULL),
(11902, 1190, '0,1100,1190', '钱包加款', 'B', NULL, '', NULL, 'proxy:wallet:recharge',
NULL, NULL, 1, 2, '', NULL, now(), now(), NULL),
(11910, 1190, '0,1100,1190', '充值订单', 'C', 'ProxyWalletRecharge', 'wallet-recharge', 'proxy/wallet-recharge/index', NULL,
0, 1, 1, 3, 'wallet', NULL, now(), now(), NULL),
(11911, 11910, '0,1100,1190,11910', '充值订单查询', 'B', NULL, '', NULL, 'proxy:wallet:recharge-order:list',
NULL, NULL, 1, 1, '', NULL, now(), now(), NULL)
ON DUPLICATE KEY UPDATE
`name` = VALUES(`name`),
`route_name` = VALUES(`route_name`),
`route_path` = VALUES(`route_path`),
`component` = VALUES(`component`),
`perm` = VALUES(`perm`),
`keep_alive` = VALUES(`keep_alive`),
`visible` = VALUES(`visible`),
`sort` = VALUES(`sort`),
`update_time` = now();
-- 分销管理(页面和接口后续继续补齐)
INSERT INTO `sys_menu`
(`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`,
`always_show`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`)
VALUES
(1200, 1100, '0,1100', '分销管理', 'M', 'ProxyDistribution', 'distribution', 'proxy/distribution/index', NULL,
0, 1, 1, 10, 'user-filled', NULL, now(), now(), NULL),
(12001, 1200, '0,1100,1200', '分销配置查询', 'B', NULL, '', NULL, 'proxy:distribution:list',
NULL, NULL, 1, 1, '', NULL, now(), now(), NULL),
(12002, 1200, '0,1100,1200', '分销配置维护', 'B', NULL, '', NULL, 'proxy:distribution:update',
NULL, NULL, 1, 2, '', NULL, now(), now(), NULL),
(12003, 1200, '0,1100,1200', '佣金流水查询', 'B', NULL, '', NULL, 'proxy:distribution:commission:list',
NULL, NULL, 1, 3, '', NULL, now(), now(), NULL)
ON DUPLICATE KEY UPDATE
`name` = VALUES(`name`),
`route_name` = VALUES(`route_name`),
`route_path` = VALUES(`route_path`),
`component` = VALUES(`component`),
`perm` = VALUES(`perm`),
`keep_alive` = VALUES(`keep_alive`),
`visible` = VALUES(`visible`),
`sort` = VALUES(`sort`),
`update_time` = now();
-- 系统管理员授权
INSERT IGNORE INTO `sys_role_menu` (`role_id`, `menu_id`) VALUES
(2, 1100),
(2, 1110), (2, 11101), (2, 11102), (2, 11103), (2, 11104),
(2, 1120), (2, 11201), (2, 11202), (2, 11203), (2, 11204),
(2, 1130), (2, 11301), (2, 11302), (2, 11303), (2, 11304),
(2, 1140), (2, 11401), (2, 11402), (2, 11403), (2, 11404),
(2, 1150), (2, 11501), (2, 11502), (2, 11503), (2, 11504), (2, 11505),
(2, 1160), (2, 11601), (2, 11602), (2, 11603),
(2, 1170), (2, 11701), (2, 11702),
(2, 1180), (2, 11801), (2, 11802), (2, 11803), (2, 11804),
(2, 1190), (2, 11901), (2, 11902),
(2, 1200), (2, 12001), (2, 12002), (2, 12003);
@@ -0,0 +1,21 @@
USE youlai_admin;
-- 订单待支付取消能力升级脚本
-- 说明:
-- 1. 新增“订单取消”按钮权限,允许后台直接取消待支付订单。
INSERT INTO `sys_menu`
(`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`,
`always_show`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`)
VALUES
(11505, 1150, '0,1100,1150', '订单取消', 'B', NULL, '', NULL, 'proxy:order:cancel',
NULL, NULL, 1, 5, '', NULL, NOW(), NOW(), NULL)
ON DUPLICATE KEY UPDATE
`name` = VALUES(`name`),
`perm` = VALUES(`perm`),
`visible` = VALUES(`visible`),
`sort` = VALUES(`sort`),
`update_time` = NOW();
INSERT IGNORE INTO `sys_role_menu` (`role_id`, `menu_id`) VALUES
(2, 11505);
@@ -0,0 +1,29 @@
USE youlai_admin;
-- 订单补偿记录菜单升级脚本
-- 说明:
-- 1. 在代理平台下新增“订单补偿记录”菜单。
-- 2. 用于查看订单补偿尝试历史、失败原因和关联退款流水。
INSERT INTO `sys_menu`
(`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`,
`always_show`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`)
VALUES
(1210, 1100, '0,1100', '订单补偿记录', 'M', 'ProxyOrderCompensationRecord', 'order-compensation-record', 'proxy/order-compensation-record/index', NULL,
0, 1, 1, 12, 'warning', NULL, NOW(), NOW(), NULL),
(12101, 1210, '0,1100,1210', '订单补偿记录查询', 'B', NULL, '', NULL, 'proxy:order-compensation-record:list',
NULL, NULL, 1, 1, '', NULL, NOW(), NOW(), NULL)
ON DUPLICATE KEY UPDATE
`name` = VALUES(`name`),
`route_name` = VALUES(`route_name`),
`route_path` = VALUES(`route_path`),
`component` = VALUES(`component`),
`perm` = VALUES(`perm`),
`keep_alive` = VALUES(`keep_alive`),
`visible` = VALUES(`visible`),
`sort` = VALUES(`sort`),
`icon` = VALUES(`icon`),
`update_time` = NOW();
INSERT IGNORE INTO `sys_role_menu` (`role_id`, `menu_id`) VALUES
(2, 1210), (2, 12101);
@@ -0,0 +1,52 @@
USE youlai_admin;
-- 订单异常补偿闭环升级脚本
-- 说明:
-- 1. 为订单主表补充补偿状态、补偿说明、重试次数和最近补偿时间。
-- 2. 新增订单补偿记录表,保留每次退款补偿尝试的结果。
-- 3. 新增“订单补偿重试”按钮权限,方便后台人工重试失败补偿。
ALTER TABLE `xxk_proxy_order`
ADD COLUMN `compensation_status` VARCHAR(32) NOT NULL DEFAULT 'NONE' COMMENT '补偿状态(NONE无需补偿 PENDING待补偿 SUCCESS补偿成功 FAIL补偿失败)' AFTER `open_status`,
ADD COLUMN `compensation_reason` VARCHAR(255) NULL COMMENT '补偿说明' AFTER `remark`,
ADD COLUMN `compensation_retry_count` INT NOT NULL DEFAULT 0 COMMENT '补偿重试次数' AFTER `compensation_reason`,
ADD COLUMN `last_compensation_time` DATETIME NULL COMMENT '最近补偿时间' AFTER `compensation_retry_count`,
ADD KEY `idx_xxk_proxy_order_compensation_status` (`compensation_status`, `update_time`);
CREATE TABLE `xxk_order_compensation_record` (
`id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`order_id` BIGINT NOT NULL COMMENT '订单ID',
`order_no` VARCHAR(64) NOT NULL COMMENT '订单号',
`compensation_type` VARCHAR(32) NOT NULL COMMENT '补偿类型',
`attempt_no` INT NOT NULL DEFAULT 1 COMMENT '第几次补偿尝试',
`compensation_status` VARCHAR(32) NOT NULL DEFAULT 'PENDING' COMMENT '补偿状态(PENDING/SUCCESS/FAIL)',
`reason` VARCHAR(255) DEFAULT NULL COMMENT '补偿原因',
`error_message` VARCHAR(500) DEFAULT NULL COMMENT '失败原因',
`related_flow_no` VARCHAR(64) DEFAULT NULL COMMENT '关联退款流水号',
`operator_id` BIGINT DEFAULT NULL COMMENT '操作人ID',
`operator_name` VARCHAR(100) DEFAULT NULL COMMENT '操作人名称',
`execute_time` DATETIME DEFAULT NULL COMMENT '执行时间',
`create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`is_deleted` TINYINT NOT NULL DEFAULT 0 COMMENT '是否删除(0否 1是)',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_xxk_order_compensation_attempt` (`order_id`, `compensation_type`, `attempt_no`),
KEY `idx_xxk_order_compensation_order_no` (`order_no`),
KEY `idx_xxk_order_compensation_status` (`compensation_status`, `execute_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='订单补偿记录表';
INSERT INTO `sys_menu`
(`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`,
`always_show`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`)
VALUES
(11504, 1150, '0,1100,1150', '订单补偿重试', 'B', NULL, '', NULL, 'proxy:order:compensate',
NULL, NULL, 1, 4, '', NULL, NOW(), NOW(), NULL)
ON DUPLICATE KEY UPDATE
`name` = VALUES(`name`),
`perm` = VALUES(`perm`),
`visible` = VALUES(`visible`),
`sort` = VALUES(`sort`),
`update_time` = NOW();
INSERT IGNORE INTO `sys_role_menu` (`role_id`, `menu_id`) VALUES
(2, 11504);
@@ -0,0 +1,50 @@
USE youlai_admin;
-- 订单支付对外适配升级脚本
-- 说明:
-- 1. 新增订单支付记录表,承接支付中台 / 自研支付的统一支付单。
-- 2. 新增支付回调日志表,保留异步通知原文和处理结果。
-- 3. 当前主订单表已存在 payment_type 字段,本脚本不重复新增。
CREATE TABLE IF NOT EXISTS `xxk_proxy_order_pay` (
`id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`pay_order_no` VARCHAR(64) NOT NULL COMMENT '支付单号',
`order_id` BIGINT NOT NULL COMMENT '业务订单ID',
`order_no` VARCHAR(64) NOT NULL COMMENT '业务订单号',
`payment_type` VARCHAR(32) NOT NULL COMMENT '支付方式(BALANCE/ALIPAY/WECHAT/EXTERNAL)',
`pay_status` VARCHAR(32) NOT NULL DEFAULT 'INIT' COMMENT '支付单状态(INIT/PAYING/PAID/FAIL/CLOSED/REFUNDED)',
`channel_order_no` VARCHAR(128) DEFAULT NULL COMMENT '渠道支付单号',
`channel_response` TEXT DEFAULT NULL COMMENT '渠道原始响应',
`client_type` VARCHAR(32) DEFAULT NULL COMMENT '客户端类型(PC/H5/APP/MINI_PROGRAM)',
`return_url` VARCHAR(500) DEFAULT NULL COMMENT '支付完成跳转地址',
`amount` DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '支付金额',
`currency` VARCHAR(16) DEFAULT NULL COMMENT '币种',
`paid_time` DATETIME DEFAULT NULL COMMENT '支付成功时间',
`expire_time` DATETIME DEFAULT NULL COMMENT '支付过期时间',
`remark` VARCHAR(255) DEFAULT NULL COMMENT '备注',
`create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`is_deleted` TINYINT NOT NULL DEFAULT 0 COMMENT '是否删除(0否 1是)',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_xxk_proxy_order_pay_no` (`pay_order_no`),
KEY `idx_xxk_proxy_order_pay_order_no` (`order_no`),
KEY `idx_xxk_proxy_order_pay_status` (`pay_status`, `update_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='订单支付记录表';
CREATE TABLE IF NOT EXISTS `xxk_proxy_payment_notify_log` (
`id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`pay_order_no` VARCHAR(64) DEFAULT NULL COMMENT '支付单号',
`order_no` VARCHAR(64) DEFAULT NULL COMMENT '业务订单号',
`payment_type` VARCHAR(32) DEFAULT NULL COMMENT '支付方式',
`notify_type` VARCHAR(32) DEFAULT NULL COMMENT '通知类型(PAY/REFUND/CLOSE)',
`notify_body` MEDIUMTEXT DEFAULT NULL COMMENT '通知原文',
`verify_status` VARCHAR(16) NOT NULL DEFAULT 'INIT' COMMENT '验签状态(INIT/SUCCESS/FAIL)',
`process_status` VARCHAR(16) NOT NULL DEFAULT 'INIT' COMMENT '处理状态(INIT/SUCCESS/FAIL)',
`error_message` VARCHAR(500) DEFAULT NULL COMMENT '失败原因',
`create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`is_deleted` TINYINT NOT NULL DEFAULT 0 COMMENT '是否删除(0否 1是)',
PRIMARY KEY (`id`),
KEY `idx_xxk_proxy_payment_notify_order_no` (`order_no`),
KEY `idx_xxk_proxy_payment_notify_status` (`verify_status`, `process_status`, `update_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='支付回调日志表';
+541
View File
@@ -0,0 +1,541 @@
# XXK (MySQL 5.7 ~ MySQL 8.x)
#
# 1. SQL youlai_admin
# 2. 使 xxk_
# 3. IP IP
USE youlai_admin;
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for xxk_upstream_provider
-- ----------------------------
DROP TABLE IF EXISTS `xxk_upstream_provider`;
CREATE TABLE `xxk_upstream_provider` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键',
`provider_code` varchar(64) NOT NULL COMMENT '供应商编码',
`provider_name` varchar(100) NOT NULL COMMENT '供应商名称',
`provider_type` varchar(32) NOT NULL COMMENT '供应商类型',
`base_url` varchar(255) NOT NULL COMMENT '接口基础地址',
`auth_user_id` varchar(100) DEFAULT NULL COMMENT '上游UserId',
`auth_token` varchar(255) DEFAULT NULL COMMENT '上游Token',
`success_codes` varchar(100) DEFAULT '1000,2001' COMMENT '成功业务码集合',
`http_timeout_ms` int DEFAULT 10000 COMMENT '超时时间(毫秒)',
`status` tinyint DEFAULT 1 COMMENT '状态(1-启用 0-禁用)',
`is_default` tinyint DEFAULT 0 COMMENT '默认供应商(1-是 0-否)',
`remark` varchar(255) DEFAULT NULL COMMENT '备注',
`create_by` bigint DEFAULT NULL COMMENT '创建人ID',
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
`update_by` bigint DEFAULT NULL COMMENT '更新人ID',
`update_time` datetime DEFAULT NULL COMMENT '更新时间',
`is_deleted` tinyint DEFAULT 0 COMMENT '逻辑删除(1-已删除 0-未删除)',
PRIMARY KEY (`id`) USING BTREE,
UNIQUE KEY `uk_provider_code` (`provider_code`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='上游供应商配置表';
-- ----------------------------
-- Table structure for xxk_product
-- ----------------------------
DROP TABLE IF EXISTS `xxk_product`;
CREATE TABLE `xxk_product` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键',
`product_code` varchar(64) NOT NULL COMMENT '商品编码',
`product_name` varchar(100) NOT NULL COMMENT '商品名称',
`product_type` varchar(32) NOT NULL COMMENT '商品类型(STATIC_RESIDENTIAL/DYNAMIC_RESIDENTIAL)',
`upstream_provider_id` bigint NOT NULL COMMENT '上游供应商ID',
`upstream_product_code` varchar(64) DEFAULT NULL COMMENT '上游商品编码',
`proxies_type` varchar(64) DEFAULT NULL COMMENT '上游代理类型',
`proxies_format` varchar(64) DEFAULT NULL COMMENT '上游代理格式',
`purpose_web` varchar(64) DEFAULT NULL COMMENT '上游用途参数',
`upstream_params` text DEFAULT NULL COMMENT '上游扩展参数JSON',
`upstream_capabilities` text DEFAULT NULL COMMENT '上游能力JSON',
`protocols_type` tinyint DEFAULT NULL COMMENT '协议类型(1-HTTP 2-SOCKS5 3-HTTP+SOCKS5)',
`udp_status` tinyint DEFAULT 0 COMMENT '是否启用UDP(1-是 0-否)',
`currency` varchar(10) DEFAULT 'RMB' COMMENT '币种',
`enable_distribution` tinyint DEFAULT 1 COMMENT '是否参与分销(1-是 0-否)',
`status` tinyint DEFAULT 1 COMMENT '状态(1-上架 0-下架)',
`sort` int DEFAULT 0 COMMENT '排序',
`remark` varchar(255) DEFAULT NULL COMMENT '备注',
`create_by` bigint DEFAULT NULL COMMENT '创建人ID',
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
`update_by` bigint DEFAULT NULL COMMENT '更新人ID',
`update_time` datetime DEFAULT NULL COMMENT '更新时间',
`is_deleted` tinyint DEFAULT 0 COMMENT '逻辑删除(1-已删除 0-未删除)',
PRIMARY KEY (`id`) USING BTREE,
UNIQUE KEY `uk_product_code` (`product_code`) USING BTREE,
KEY `idx_product_type_status` (`product_type`, `status`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='商品主表';
-- ----------------------------
-- Table structure for xxk_product_static_country_price
-- ----------------------------
DROP TABLE IF EXISTS `xxk_product_static_country_price`;
CREATE TABLE `xxk_product_static_country_price` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键',
`product_id` bigint NOT NULL COMMENT '商品ID',
`price_type` varchar(20) DEFAULT NULL COMMENT '价格类型(DEFAULT默认/NODE特殊节点)',
`qiyun_product_type` varchar(32) DEFAULT NULL COMMENT '齐云产品类型',
`qiyun_pid` varchar(64) DEFAULT NULL COMMENT '齐云项目ID',
`qiyun_project_name` varchar(255) DEFAULT NULL COMMENT '齐云项目名称',
`qiyun_area_id` varchar(64) DEFAULT NULL COMMENT '齐云省份ID',
`qiyun_area_name` varchar(255) DEFAULT NULL COMMENT '齐云省份名称',
`qiyun_node_id` varchar(100) DEFAULT NULL COMMENT '齐云节点ID',
`qiyun_node_name` varchar(255) DEFAULT NULL COMMENT '齐云节点名称',
`region_id` bigint DEFAULT NULL COMMENT '地区ID',
`region_code` varchar(64) DEFAULT NULL COMMENT '地区编号',
`region_name` varchar(100) DEFAULT NULL COMMENT '地区名称',
`region_name_zh` varchar(100) DEFAULT NULL COMMENT '地区中文名',
`country_code` varchar(10) NOT NULL COMMENT '国家编码',
`country_name` varchar(100) NOT NULL COMMENT '国家名称',
`base_price` decimal(18,2) NOT NULL DEFAULT 0.00 COMMENT '地区基础单价',
`currency` varchar(10) DEFAULT 'RMB' COMMENT '币种',
`status` tinyint DEFAULT 1 COMMENT '状态(1-启用 0-禁用)',
`sort` int DEFAULT 0 COMMENT '排序',
`remark` varchar(255) DEFAULT NULL COMMENT '备注',
`create_by` bigint DEFAULT NULL COMMENT '创建人ID',
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
`update_by` bigint DEFAULT NULL COMMENT '更新人ID',
`update_time` datetime DEFAULT NULL COMMENT '更新时间',
`is_deleted` tinyint DEFAULT 0 COMMENT '逻辑删除(1-已删除 0-未删除)',
PRIMARY KEY (`id`) USING BTREE,
KEY `idx_product_country` (`product_id`, `country_code`) USING BTREE,
KEY `idx_static_price_qiyun_node` (`product_id`, `price_type`, `qiyun_node_id`) USING BTREE,
KEY `idx_country_code` (`country_code`) USING BTREE,
KEY `idx_static_price_region_id` (`region_id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='静态商品地区定价表';
-- ----------------------------
-- Table structure for xxk_product_duration_multiplier
-- ----------------------------
DROP TABLE IF EXISTS `xxk_product_duration_multiplier`;
CREATE TABLE `xxk_product_duration_multiplier` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键',
`product_id` bigint NOT NULL COMMENT '商品ID',
`duration_days` int NOT NULL COMMENT '兼容时长天数',
`duration_unit` varchar(16) NOT NULL DEFAULT 'DAY' COMMENT '时长单位(DAY天/HOUR小时)',
`duration_value` int NOT NULL DEFAULT 1 COMMENT '时长数值',
`multiplier` decimal(10,4) NOT NULL DEFAULT 1.0000 COMMENT '价格倍率',
`status` tinyint DEFAULT 1 COMMENT '状态(1-启用 0-禁用)',
`remark` varchar(255) DEFAULT NULL COMMENT '备注',
`create_by` bigint DEFAULT NULL COMMENT '创建人ID',
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
`update_by` bigint DEFAULT NULL COMMENT '更新人ID',
`update_time` datetime DEFAULT NULL COMMENT '更新时间',
`is_deleted` tinyint DEFAULT 0 COMMENT '逻辑删除(1-已删除 0-未删除)',
PRIMARY KEY (`id`) USING BTREE,
UNIQUE KEY `uk_product_duration_unit_value` (`product_id`, `duration_unit`, `duration_value`) USING BTREE,
KEY `idx_product_duration_days` (`product_id`, `duration_days`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='商品时长倍率表';
-- ----------------------------
-- Table structure for xxk_product_dynamic_plan
-- ----------------------------
DROP TABLE IF EXISTS `xxk_product_dynamic_plan`;
CREATE TABLE `xxk_product_dynamic_plan` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键',
`product_id` bigint NOT NULL COMMENT '商品ID',
`plan_code` varchar(64) NOT NULL COMMENT '流量套餐编码',
`flow_gb` decimal(12,3) NOT NULL DEFAULT 0.000 COMMENT '流量套餐GB',
`base_price` decimal(18,2) NOT NULL DEFAULT 0.00 COMMENT '流量套餐基础价',
`allow_sticky_session` tinyint DEFAULT 1 COMMENT '允许粘性会话(1-是 0-否)',
`allow_location_select` tinyint DEFAULT 1 COMMENT '允许地区选择(1-是 0-否)',
`allow_custom_limit` tinyint DEFAULT 0 COMMENT '允许自定义通道流量上限(1-是 0-否)',
`status` tinyint DEFAULT 1 COMMENT '状态(1-启用 0-禁用)',
`remark` varchar(255) DEFAULT NULL COMMENT '备注',
`create_by` bigint DEFAULT NULL COMMENT '创建人ID',
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
`update_by` bigint DEFAULT NULL COMMENT '更新人ID',
`update_time` datetime DEFAULT NULL COMMENT '更新时间',
`is_deleted` tinyint DEFAULT 0 COMMENT '逻辑删除(1-已删除 0-未删除)',
PRIMARY KEY (`id`) USING BTREE,
UNIQUE KEY `uk_plan_code` (`plan_code`) USING BTREE,
UNIQUE KEY `uk_product_flow` (`product_id`, `flow_gb`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='动态商品流量套餐表';
-- ----------------------------
-- Table structure for xxk_user_profile
-- ----------------------------
DROP TABLE IF EXISTS `xxk_user_profile`;
CREATE TABLE `xxk_user_profile` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键',
`user_id` bigint NOT NULL COMMENT '关联sys_user.id',
`user_type` varchar(32) DEFAULT 'CUSTOMER' COMMENT '用户类型',
`invite_code` varchar(32) DEFAULT NULL COMMENT '邀请码',
`register_source` varchar(32) DEFAULT 'ADMIN' COMMENT '注册来源',
`register_ip` varchar(64) DEFAULT NULL COMMENT '注册IP',
`last_login_ip` varchar(64) DEFAULT NULL COMMENT '最后登录IP',
`last_login_time` datetime DEFAULT NULL COMMENT '最后登录时间',
`status` tinyint DEFAULT 1 COMMENT '状态(1-正常 0-禁用)',
`remark` varchar(255) DEFAULT NULL COMMENT '备注',
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
`update_time` datetime DEFAULT NULL COMMENT '更新时间',
`is_deleted` tinyint DEFAULT 0 COMMENT '逻辑删除(1-已删除 0-未删除)',
PRIMARY KEY (`id`) USING BTREE,
UNIQUE KEY `uk_user_id` (`user_id`) USING BTREE,
UNIQUE KEY `uk_invite_code` (`invite_code`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户业务扩展表';
-- ----------------------------
-- Table structure for xxk_wallet_account
-- ----------------------------
DROP TABLE IF EXISTS `xxk_wallet_account`;
CREATE TABLE `xxk_wallet_account` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键',
`user_id` bigint NOT NULL COMMENT '用户ID',
`balance` decimal(18,2) NOT NULL DEFAULT 0.00 COMMENT '可用余额',
`frozen_balance` decimal(18,2) NOT NULL DEFAULT 0.00 COMMENT '冻结余额',
`total_recharge_amount` decimal(18,2) NOT NULL DEFAULT 0.00 COMMENT '累计充值金额',
`total_consume_amount` decimal(18,2) NOT NULL DEFAULT 0.00 COMMENT '累计消费金额',
`total_refund_amount` decimal(18,2) NOT NULL DEFAULT 0.00 COMMENT '累计退款金额',
`status` tinyint DEFAULT 1 COMMENT '状态(1-正常 0-禁用)',
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
`update_time` datetime DEFAULT NULL COMMENT '更新时间',
PRIMARY KEY (`id`) USING BTREE,
UNIQUE KEY `uk_wallet_user_id` (`user_id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='钱包账户表';
-- ----------------------------
-- Table structure for xxk_wallet_flow
-- ----------------------------
DROP TABLE IF EXISTS `xxk_wallet_flow`;
CREATE TABLE `xxk_wallet_flow` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键',
`flow_no` varchar(64) NOT NULL COMMENT '流水号',
`user_id` bigint NOT NULL COMMENT '用户ID',
`biz_type` varchar(32) NOT NULL COMMENT '业务类型',
`change_type` varchar(16) NOT NULL COMMENT '变动类型(IN/OUT)',
`change_amount` decimal(18,2) NOT NULL DEFAULT 0.00 COMMENT '变动金额',
`before_balance` decimal(18,2) NOT NULL DEFAULT 0.00 COMMENT '变动前余额',
`after_balance` decimal(18,2) NOT NULL DEFAULT 0.00 COMMENT '变动后余额',
`related_order_no` varchar(64) DEFAULT NULL COMMENT '关联订单号',
`remark` varchar(255) DEFAULT NULL COMMENT '备注',
`operate_by` bigint DEFAULT NULL COMMENT '操作人ID',
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
`update_time` datetime DEFAULT NULL COMMENT '更新时间',
PRIMARY KEY (`id`) USING BTREE,
UNIQUE KEY `uk_flow_no` (`flow_no`) USING BTREE,
KEY `idx_wallet_user_time` (`user_id`, `create_time`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='钱包流水表';
-- ----------------------------
-- Table structure for xxk_proxy_order
-- ----------------------------
DROP TABLE IF EXISTS `xxk_proxy_order`;
CREATE TABLE `xxk_proxy_order` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键',
`order_no` varchar(64) NOT NULL COMMENT '订单号',
`user_id` bigint NOT NULL COMMENT '用户ID',
`product_id` bigint NOT NULL COMMENT '商品ID',
`product_type` varchar(32) NOT NULL COMMENT '商品类型',
`order_type` varchar(16) NOT NULL DEFAULT 'NEW' COMMENT '订单类型(NEW/RENEW)',
`order_status` varchar(32) NOT NULL DEFAULT 'WAIT_PAY' COMMENT '订单状态',
`pay_status` varchar(32) NOT NULL DEFAULT 'UNPAID' COMMENT '支付状态',
`open_status` varchar(32) NOT NULL DEFAULT 'WAIT_OPEN' COMMENT '开通状态',
`payment_type` varchar(16) DEFAULT 'BALANCE' COMMENT '支付方式',
`currency` varchar(10) DEFAULT 'RMB' COMMENT '币种',
`buy_quantity` int NOT NULL DEFAULT 1 COMMENT '购买数量',
`duration_days` int NOT NULL COMMENT '购买天数',
`sale_amount` decimal(18,2) NOT NULL DEFAULT 0.00 COMMENT '销售金额',
`paid_amount` decimal(18,2) NOT NULL DEFAULT 0.00 COMMENT '实付金额',
`cost_amount` decimal(18,2) DEFAULT 0.00 COMMENT '采购成本',
`open_success_amount` decimal(18,2) DEFAULT 0.00 COMMENT '开通成功金额',
`region_snapshot` text COMMENT '地区快照',
`product_snapshot` mediumtext COMMENT '商品快照',
`inviter_user_id` bigint DEFAULT NULL COMMENT '一级邀请人',
`parent_inviter_user_id` bigint DEFAULT NULL COMMENT '二级邀请人',
`remark` varchar(255) DEFAULT NULL COMMENT '备注',
`pay_time` datetime DEFAULT NULL COMMENT '支付时间',
`open_time` datetime DEFAULT NULL COMMENT '开通时间',
`finish_time` datetime DEFAULT NULL COMMENT '完成时间',
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
`update_time` datetime DEFAULT NULL COMMENT '更新时间',
`is_deleted` tinyint DEFAULT 0 COMMENT '逻辑删除(1-已删除 0-未删除)',
PRIMARY KEY (`id`) USING BTREE,
UNIQUE KEY `uk_order_no` (`order_no`) USING BTREE,
KEY `idx_order_user_time` (`user_id`, `create_time`) USING BTREE,
KEY `idx_order_status` (`order_status`, `pay_status`, `open_status`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='代理订单主表';
-- ----------------------------
-- Table structure for xxk_proxy_order_item
-- ----------------------------
DROP TABLE IF EXISTS `xxk_proxy_order_item`;
CREATE TABLE `xxk_proxy_order_item` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键',
`order_id` bigint NOT NULL COMMENT '订单ID',
`order_no` varchar(64) NOT NULL COMMENT '订单号',
`user_id` bigint NOT NULL COMMENT '用户ID',
`product_id` bigint NOT NULL COMMENT '商品ID',
`product_type` varchar(32) NOT NULL COMMENT '商品类型',
`country_code` varchar(10) DEFAULT NULL COMMENT '国家编码',
`country_name` varchar(100) DEFAULT NULL COMMENT '国家名称',
`quantity` int NOT NULL DEFAULT 1 COMMENT '数量',
`duration_days` int NOT NULL COMMENT '时长天数',
`duration_multiplier` decimal(10,4) NOT NULL DEFAULT 1.0000 COMMENT '时长倍率',
`unit_price` decimal(18,2) NOT NULL DEFAULT 0.00 COMMENT '单价',
`line_amount` decimal(18,2) NOT NULL DEFAULT 0.00 COMMENT '小计金额',
`flow_gb` decimal(12,3) DEFAULT NULL COMMENT '动态流量GB',
`upstream_payload` mediumtext COMMENT '上游请求快照',
`item_status` varchar(32) DEFAULT 'INIT' COMMENT '订单项状态',
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
`update_time` datetime DEFAULT NULL COMMENT '更新时间',
`is_deleted` tinyint DEFAULT 0 COMMENT '逻辑删除(1-已删除 0-未删除)',
PRIMARY KEY (`id`) USING BTREE,
KEY `idx_order_item_order_id` (`order_id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='代理订单项表';
-- ----------------------------
-- Table structure for xxk_static_proxy_asset
-- ----------------------------
DROP TABLE IF EXISTS `xxk_static_proxy_asset`;
CREATE TABLE `xxk_static_proxy_asset` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键',
`user_id` bigint NOT NULL COMMENT '用户ID',
`order_id` bigint NOT NULL COMMENT '订单ID',
`order_item_id` bigint DEFAULT NULL COMMENT '订单项ID',
`product_id` bigint NOT NULL COMMENT '商品ID',
`upstream_provider_id` bigint NOT NULL COMMENT '上游供应商ID',
`upstream_order_no` varchar(64) DEFAULT NULL COMMENT '上游订单号',
`upstream_proxy_id` varchar(64) NOT NULL COMMENT '上游代理ID',
`proxy_address` varchar(100) NOT NULL COMMENT '代理IP',
`port` int NOT NULL COMMENT '端口',
`username` varchar(100) DEFAULT NULL COMMENT '账号',
`password` varchar(100) DEFAULT NULL COMMENT '密码',
`protocols` varchar(32) DEFAULT NULL COMMENT '协议',
`country_code` varchar(10) DEFAULT NULL COMMENT '国家编码',
`country_name` varchar(100) DEFAULT NULL COMMENT '国家名称',
`city_name` varchar(100) DEFAULT NULL COMMENT '城市名称',
`proxy_status` tinyint DEFAULT 1 COMMENT '代理状态(1-正常 2-禁用 3-维护)',
`is_auto_renew` tinyint DEFAULT 0 COMMENT '自动续费(1-开 0-关)',
`expired_at` datetime DEFAULT NULL COMMENT '到期时间',
`last_sync_time` datetime DEFAULT NULL COMMENT '最后同步时间',
`raw_data` mediumtext COMMENT '原始数据快照',
`status` tinyint DEFAULT 1 COMMENT '业务状态(1-有效 0-无效)',
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
`update_time` datetime DEFAULT NULL COMMENT '更新时间',
`is_deleted` tinyint DEFAULT 0 COMMENT '逻辑删除(1-已删除 0-未删除)',
PRIMARY KEY (`id`) USING BTREE,
UNIQUE KEY `uk_static_upstream_proxy_id` (`upstream_proxy_id`) USING BTREE,
KEY `idx_static_user_status` (`user_id`, `status`) USING BTREE,
KEY `idx_static_expired_at` (`expired_at`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='静态代理资产表';
-- ----------------------------
-- Table structure for xxk_static_proxy_whitelist
-- ----------------------------
DROP TABLE IF EXISTS `xxk_static_proxy_whitelist`;
CREATE TABLE `xxk_static_proxy_whitelist` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键',
`user_id` bigint NOT NULL COMMENT '用户ID',
`product_id` bigint DEFAULT NULL COMMENT '商品ID',
`proxy_type` varchar(64) DEFAULT NULL COMMENT '静态代理类型',
`whitelist_ip` varchar(64) NOT NULL COMMENT '白名单IP',
`upstream_address_id` varchar(64) DEFAULT NULL COMMENT '上游白名单记录ID',
`status` tinyint DEFAULT 1 COMMENT '状态(1-有效 0-删除)',
`remark` varchar(255) DEFAULT NULL COMMENT '备注',
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
`update_time` datetime DEFAULT NULL COMMENT '更新时间',
PRIMARY KEY (`id`) USING BTREE,
UNIQUE KEY `uk_user_whitelist_ip` (`user_id`, `whitelist_ip`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='静态代理白名单表';
-- ----------------------------
-- Table structure for xxk_static_proxy_change_log
-- ----------------------------
DROP TABLE IF EXISTS `xxk_static_proxy_change_log`;
CREATE TABLE `xxk_static_proxy_change_log` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键',
`asset_id` bigint NOT NULL COMMENT '静态资产ID',
`user_id` bigint NOT NULL COMMENT '用户ID',
`change_type` varchar(32) NOT NULL COMMENT '变更类型',
`before_value` text COMMENT '变更前',
`after_value` text COMMENT '变更后',
`operator_id` bigint DEFAULT NULL COMMENT '操作人ID',
`remark` varchar(255) DEFAULT NULL COMMENT '备注',
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
PRIMARY KEY (`id`) USING BTREE,
KEY `idx_static_change_asset_id` (`asset_id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='静态代理变更日志表';
-- ----------------------------
-- Table structure for xxk_dynamic_channel
-- ----------------------------
DROP TABLE IF EXISTS `xxk_dynamic_channel`;
CREATE TABLE `xxk_dynamic_channel` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键',
`user_id` bigint NOT NULL COMMENT '用户ID',
`order_id` bigint NOT NULL COMMENT '订单ID',
`order_item_id` bigint DEFAULT NULL COMMENT '订单项ID',
`product_id` bigint NOT NULL COMMENT '商品ID',
`upstream_provider_id` bigint NOT NULL COMMENT '上游供应商ID',
`upstream_channel_id` varchar(64) NOT NULL COMMENT '上游通道ID',
`channel_name` varchar(100) NOT NULL COMMENT '通道名称',
`channel_password` varchar(100) DEFAULT NULL COMMENT '通道密码',
`channel_status` varchar(16) NOT NULL DEFAULT 'ENABLED' COMMENT '通道状态',
`flow_package_gb` decimal(12,3) NOT NULL DEFAULT 0.000 COMMENT '购买流量包GB',
`traffic_limit_gb` decimal(12,3) NOT NULL DEFAULT 0.000 COMMENT '通道流量上限GB',
`used_traffic_gb` decimal(12,3) NOT NULL DEFAULT 0.000 COMMENT '已用流量GB',
`remaining_traffic_gb` decimal(12,3) NOT NULL DEFAULT 0.000 COMMENT '剩余流量GB',
`expired_at` datetime DEFAULT NULL COMMENT '到期时间',
`allow_generate_proxy` tinyint DEFAULT 1 COMMENT '允许生成代理(1-是 0-否)',
`last_sync_time` datetime DEFAULT NULL COMMENT '最后同步时间',
`raw_data` mediumtext COMMENT '原始数据快照',
`status` tinyint DEFAULT 1 COMMENT '业务状态(1-有效 0-无效)',
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
`update_time` datetime DEFAULT NULL COMMENT '更新时间',
`is_deleted` tinyint DEFAULT 0 COMMENT '逻辑删除(1-已删除 0-未删除)',
PRIMARY KEY (`id`) USING BTREE,
UNIQUE KEY `uk_dynamic_upstream_channel_id` (`upstream_channel_id`) USING BTREE,
KEY `idx_dynamic_user_status` (`user_id`, `status`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='动态通道表';
-- ----------------------------
-- Table structure for xxk_dynamic_channel_traffic_log
-- ----------------------------
DROP TABLE IF EXISTS `xxk_dynamic_channel_traffic_log`;
CREATE TABLE `xxk_dynamic_channel_traffic_log` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键',
`channel_id` bigint NOT NULL COMMENT '动态通道ID',
`user_id` bigint NOT NULL COMMENT '用户ID',
`date_type` tinyint DEFAULT NULL COMMENT '查询日期类型',
`start_date` date DEFAULT NULL COMMENT '开始日期',
`end_date` date DEFAULT NULL COMMENT '结束日期',
`used_traffic_gb` decimal(12,3) NOT NULL DEFAULT 0.000 COMMENT '已用流量GB',
`total_traffic_gb` decimal(12,3) DEFAULT 0.000 COMMENT '累计流量GB',
`raw_data` mediumtext COMMENT '原始返回',
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
PRIMARY KEY (`id`) USING BTREE,
KEY `idx_channel_traffic_channel_id` (`channel_id`, `create_time`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='动态通道流量日志表';
-- ----------------------------
-- Table structure for xxk_dynamic_proxy_generate_log
-- ----------------------------
DROP TABLE IF EXISTS `xxk_dynamic_proxy_generate_log`;
CREATE TABLE `xxk_dynamic_proxy_generate_log` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键',
`channel_id` bigint NOT NULL COMMENT '动态通道ID',
`user_id` bigint NOT NULL COMMENT '用户ID',
`location` varchar(32) NOT NULL COMMENT '地区参数',
`domain` varchar(32) DEFAULT 'Global' COMMENT '域名参数',
`sticky_session_time` int NOT NULL DEFAULT 0 COMMENT '粘性会话分钟',
`proxy_count` int NOT NULL DEFAULT 1 COMMENT '生成数量',
`state` varchar(100) DEFAULT NULL COMMENT '',
`city` varchar(100) DEFAULT NULL COMMENT '城市',
`generated_result` mediumtext COMMENT '生成结果',
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
PRIMARY KEY (`id`) USING BTREE,
KEY `idx_generate_channel_id` (`channel_id`, `create_time`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='动态代理生成日志表';
-- ----------------------------
-- Table structure for xxk_distribution_relation
-- ----------------------------
DROP TABLE IF EXISTS `xxk_distribution_relation`;
CREATE TABLE `xxk_distribution_relation` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键',
`user_id` bigint DEFAULT NULL COMMENT '当前用户ID',
`parent_user_id` bigint DEFAULT NULL COMMENT '一级上级用户ID',
`grand_parent_user_id` bigint DEFAULT NULL COMMENT '二级上级用户ID',
`bind_source` varchar(32) DEFAULT NULL COMMENT '绑定来源',
`bind_time` datetime DEFAULT NULL COMMENT '绑定时间',
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
`update_time` datetime DEFAULT NULL COMMENT '更新时间',
`is_deleted` tinyint DEFAULT 0 COMMENT '逻辑删除(1-已删除 0-未删除)',
PRIMARY KEY (`id`) USING BTREE,
UNIQUE KEY `uk_distribution_user_id` (`user_id`) USING BTREE,
KEY `idx_distribution_parent_user_id` (`parent_user_id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='分销关系表';
-- ----------------------------
-- Table structure for xxk_distribution_config
-- ----------------------------
DROP TABLE IF EXISTS `xxk_distribution_config`;
CREATE TABLE `xxk_distribution_config` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键',
`distribution_enabled` tinyint DEFAULT 0 COMMENT '是否启用分销(1-是 0-否)',
`first_level_rate` decimal(10,4) NOT NULL DEFAULT 0.0000 COMMENT '一级佣金比例',
`second_level_rate` decimal(10,4) NOT NULL DEFAULT 0.0000 COMMENT '二级佣金比例',
`withdraw_threshold` decimal(10,2) NOT NULL DEFAULT 0.00 COMMENT '提现门槛金额',
`withdraw_fee_rate` decimal(10,4) NOT NULL DEFAULT 0.0000 COMMENT '提现手续费比例',
`settle_rule` varchar(32) DEFAULT 'OPEN_SUCCESS_AMOUNT' COMMENT '结算规则',
`status` tinyint DEFAULT 1 COMMENT '状态(1-启用 0-禁用)',
`remark` varchar(255) DEFAULT NULL COMMENT '备注',
`create_by` bigint DEFAULT NULL COMMENT '创建人ID',
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
`update_by` bigint DEFAULT NULL COMMENT '更新人ID',
`update_time` datetime DEFAULT NULL COMMENT '更新时间',
PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='分销配置表';
-- ----------------------------
-- Table structure for xxk_distribution_commission
-- ----------------------------
DROP TABLE IF EXISTS `xxk_distribution_commission`;
CREATE TABLE `xxk_distribution_commission` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键',
`user_id` bigint NOT NULL COMMENT '佣金归属用户ID',
`from_user_id` bigint NOT NULL COMMENT '消费用户ID',
`order_id` bigint NOT NULL COMMENT '订单ID',
`order_no` varchar(64) NOT NULL COMMENT '订单号',
`level_no` tinyint NOT NULL COMMENT '层级(1/2)',
`rate` decimal(10,4) NOT NULL DEFAULT 0.0000 COMMENT '佣金比例',
`base_amount` decimal(18,2) NOT NULL DEFAULT 0.00 COMMENT '佣金基数',
`commission_amount` decimal(18,2) NOT NULL DEFAULT 0.00 COMMENT '佣金金额',
`commission_status` varchar(32) NOT NULL DEFAULT 'PENDING' COMMENT '佣金状态',
`confirm_time` datetime DEFAULT NULL COMMENT '确认时间',
`settle_time` datetime DEFAULT NULL COMMENT '结算时间',
`invalid_time` datetime DEFAULT NULL COMMENT '失效时间',
`remark` varchar(255) DEFAULT NULL COMMENT '备注',
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
`update_time` datetime DEFAULT NULL COMMENT '更新时间',
PRIMARY KEY (`id`) USING BTREE,
KEY `idx_commission_user_status` (`user_id`, `commission_status`) USING BTREE,
KEY `idx_commission_order_id` (`order_id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='分销佣金流水表';
-- ----------------------------
-- Table structure for xxk_upstream_request_log
-- ----------------------------
DROP TABLE IF EXISTS `xxk_upstream_request_log`;
CREATE TABLE `xxk_upstream_request_log` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键',
`provider_id` bigint NOT NULL COMMENT '供应商ID',
`biz_type` varchar(32) NOT NULL COMMENT '业务类型',
`api_name` varchar(100) NOT NULL COMMENT '接口名称',
`request_url` varchar(255) NOT NULL COMMENT '请求地址',
`request_headers` mediumtext COMMENT '请求头',
`request_body` longtext COMMENT '请求体',
`response_body` longtext COMMENT '响应体',
`http_status` int DEFAULT NULL COMMENT 'HTTP状态码',
`biz_code` varchar(32) DEFAULT NULL COMMENT '业务码',
`success_flag` tinyint DEFAULT 0 COMMENT '是否成功(1-是 0-否)',
`duration_ms` int DEFAULT NULL COMMENT '耗时毫秒',
`error_message` varchar(500) DEFAULT NULL COMMENT '异常信息',
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
PRIMARY KEY (`id`) USING BTREE,
KEY `idx_request_provider_time` (`provider_id`, `create_time`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='上游请求日志表';
-- ----------------------------
-- Table structure for xxk_order_operate_log
-- ----------------------------
DROP TABLE IF EXISTS `xxk_order_operate_log`;
CREATE TABLE `xxk_order_operate_log` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键',
`order_id` bigint NOT NULL COMMENT '订单ID',
`order_no` varchar(64) NOT NULL COMMENT '订单号',
`operate_type` varchar(32) NOT NULL COMMENT '操作类型',
`before_status` varchar(64) DEFAULT NULL COMMENT '操作前状态',
`after_status` varchar(64) DEFAULT NULL COMMENT '操作后状态',
`content` text COMMENT '操作内容',
`operator_id` bigint DEFAULT NULL COMMENT '操作人ID',
`operator_name` varchar(100) DEFAULT NULL COMMENT '操作人名称',
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
PRIMARY KEY (`id`) USING BTREE,
KEY `idx_order_operate_order_id` (`order_id`, `create_time`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='订单操作日志表';
SET FOREIGN_KEY_CHECKS = 1;
@@ -0,0 +1,627 @@
USE youlai_admin;
-- 齐云 IP 上游适配升级脚本
SET @schema_name = DATABASE();
-- 兼容未执行会员代理归属升级脚本的数据库。
-- 当前 StaticProxyAsset 实体已包含 memberUserIdMyBatis-Plus 查询会选择 member_user_id
-- 老库缺少该字段时,静态代理自动续费/同步定时任务会报 Unknown column。
SET @column_exists = (
SELECT COUNT(1)
FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = @schema_name
AND TABLE_NAME = 'xxk_static_proxy_asset'
AND COLUMN_NAME = 'member_user_id'
);
SET @sql = IF(
@column_exists = 0,
'ALTER TABLE `xxk_static_proxy_asset` ADD COLUMN `member_user_id` BIGINT NULL COMMENT ''会员ID'' AFTER `user_id`',
'SELECT 1'
);
PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
SET @index_exists = (
SELECT COUNT(1)
FROM information_schema.STATISTICS
WHERE TABLE_SCHEMA = @schema_name
AND TABLE_NAME = 'xxk_static_proxy_asset'
AND INDEX_NAME = 'idx_xxk_static_proxy_asset_member_user_id'
);
SET @sql = IF(
@index_exists = 0,
'ALTER TABLE `xxk_static_proxy_asset` ADD INDEX `idx_xxk_static_proxy_asset_member_user_id` (`member_user_id`) USING BTREE',
'SELECT 1'
);
PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
-- 会员侧订单开通会写入 member_user_id;这些表如果仍是旧结构,会导致上游已开通但本地资源/白名单插入失败。
ALTER TABLE `xxk_dynamic_channel` MODIFY COLUMN `user_id` BIGINT NULL COMMENT '用户ID';
SET @column_exists = (
SELECT COUNT(1)
FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = @schema_name
AND TABLE_NAME = 'xxk_dynamic_channel'
AND COLUMN_NAME = 'member_user_id'
);
SET @sql = IF(
@column_exists = 0,
'ALTER TABLE `xxk_dynamic_channel` ADD COLUMN `member_user_id` BIGINT NULL COMMENT ''会员ID'' AFTER `user_id`',
'SELECT 1'
);
PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
SET @index_exists = (
SELECT COUNT(1)
FROM information_schema.STATISTICS
WHERE TABLE_SCHEMA = @schema_name
AND TABLE_NAME = 'xxk_dynamic_channel'
AND INDEX_NAME = 'idx_xxk_dynamic_channel_member_user_id'
);
SET @sql = IF(
@index_exists = 0,
'ALTER TABLE `xxk_dynamic_channel` ADD INDEX `idx_xxk_dynamic_channel_member_user_id` (`member_user_id`) USING BTREE',
'SELECT 1'
);
PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
ALTER TABLE `xxk_dynamic_channel_traffic_log` MODIFY COLUMN `user_id` BIGINT NULL COMMENT '用户ID';
SET @column_exists = (
SELECT COUNT(1)
FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = @schema_name
AND TABLE_NAME = 'xxk_dynamic_channel_traffic_log'
AND COLUMN_NAME = 'member_user_id'
);
SET @sql = IF(
@column_exists = 0,
'ALTER TABLE `xxk_dynamic_channel_traffic_log` ADD COLUMN `member_user_id` BIGINT NULL COMMENT ''会员ID'' AFTER `user_id`',
'SELECT 1'
);
PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
SET @index_exists = (
SELECT COUNT(1)
FROM information_schema.STATISTICS
WHERE TABLE_SCHEMA = @schema_name
AND TABLE_NAME = 'xxk_dynamic_channel_traffic_log'
AND INDEX_NAME = 'idx_xxk_dynamic_channel_traffic_log_member_user_id'
);
SET @sql = IF(
@index_exists = 0,
'ALTER TABLE `xxk_dynamic_channel_traffic_log` ADD INDEX `idx_xxk_dynamic_channel_traffic_log_member_user_id` (`member_user_id`) USING BTREE',
'SELECT 1'
);
PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
ALTER TABLE `xxk_dynamic_proxy_generate_log` MODIFY COLUMN `user_id` BIGINT NULL COMMENT '用户ID';
SET @column_exists = (
SELECT COUNT(1)
FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = @schema_name
AND TABLE_NAME = 'xxk_dynamic_proxy_generate_log'
AND COLUMN_NAME = 'member_user_id'
);
SET @sql = IF(
@column_exists = 0,
'ALTER TABLE `xxk_dynamic_proxy_generate_log` ADD COLUMN `member_user_id` BIGINT NULL COMMENT ''会员ID'' AFTER `user_id`',
'SELECT 1'
);
PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
SET @index_exists = (
SELECT COUNT(1)
FROM information_schema.STATISTICS
WHERE TABLE_SCHEMA = @schema_name
AND TABLE_NAME = 'xxk_dynamic_proxy_generate_log'
AND INDEX_NAME = 'idx_xxk_dynamic_proxy_generate_log_member_user_id'
);
SET @sql = IF(
@index_exists = 0,
'ALTER TABLE `xxk_dynamic_proxy_generate_log` ADD INDEX `idx_xxk_dynamic_proxy_generate_log_member_user_id` (`member_user_id`) USING BTREE',
'SELECT 1'
);
PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
ALTER TABLE `xxk_static_proxy_asset` MODIFY COLUMN `user_id` BIGINT NULL COMMENT '用户ID';
ALTER TABLE `xxk_static_proxy_whitelist` MODIFY COLUMN `user_id` BIGINT NULL COMMENT '用户ID';
SET @column_exists = (
SELECT COUNT(1)
FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = @schema_name
AND TABLE_NAME = 'xxk_static_proxy_whitelist'
AND COLUMN_NAME = 'member_user_id'
);
SET @sql = IF(
@column_exists = 0,
'ALTER TABLE `xxk_static_proxy_whitelist` ADD COLUMN `member_user_id` BIGINT NULL COMMENT ''会员ID'' AFTER `user_id`',
'SELECT 1'
);
PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
SET @index_exists = (
SELECT COUNT(1)
FROM information_schema.STATISTICS
WHERE TABLE_SCHEMA = @schema_name
AND TABLE_NAME = 'xxk_static_proxy_whitelist'
AND INDEX_NAME = 'uk_member_whitelist_ip'
);
SET @sql = IF(
@index_exists = 0,
'ALTER TABLE `xxk_static_proxy_whitelist` ADD UNIQUE KEY `uk_member_whitelist_ip` (`member_user_id`, `whitelist_ip`) USING BTREE',
'SELECT 1'
);
PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
SET @index_exists = (
SELECT COUNT(1)
FROM information_schema.STATISTICS
WHERE TABLE_SCHEMA = @schema_name
AND TABLE_NAME = 'xxk_static_proxy_whitelist'
AND INDEX_NAME = 'idx_xxk_static_proxy_whitelist_member_user_id'
);
SET @sql = IF(
@index_exists = 0,
'ALTER TABLE `xxk_static_proxy_whitelist` ADD INDEX `idx_xxk_static_proxy_whitelist_member_user_id` (`member_user_id`) USING BTREE',
'SELECT 1'
);
PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
SET @column_exists = (
SELECT COUNT(1)
FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = @schema_name
AND TABLE_NAME = 'xxk_product'
AND COLUMN_NAME = 'upstream_params'
);
SET @sql = IF(
@column_exists = 0,
'ALTER TABLE `xxk_product` ADD COLUMN `upstream_params` text DEFAULT NULL COMMENT ''上游扩展参数JSON'' AFTER `purpose_web`',
'SELECT 1'
);
PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
SET @column_exists = (
SELECT COUNT(1)
FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = @schema_name
AND TABLE_NAME = 'xxk_product'
AND COLUMN_NAME = 'upstream_capabilities'
);
SET @sql = IF(
@column_exists = 0,
'ALTER TABLE `xxk_product` ADD COLUMN `upstream_capabilities` text DEFAULT NULL COMMENT ''上游能力JSON'' AFTER `upstream_params`',
'SELECT 1'
);
PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
SET @column_exists = (
SELECT COUNT(1)
FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = @schema_name
AND TABLE_NAME = 'xxk_product_duration_multiplier'
AND COLUMN_NAME = 'duration_unit'
);
SET @sql = IF(
@column_exists = 0,
'ALTER TABLE `xxk_product_duration_multiplier` ADD COLUMN `duration_unit` varchar(16) NOT NULL DEFAULT ''DAY'' COMMENT ''时长单位(DAY天/HOUR小时)'' AFTER `duration_days`',
'SELECT 1'
);
PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
SET @column_exists = (
SELECT COUNT(1)
FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = @schema_name
AND TABLE_NAME = 'xxk_product_duration_multiplier'
AND COLUMN_NAME = 'duration_value'
);
SET @sql = IF(
@column_exists = 0,
'ALTER TABLE `xxk_product_duration_multiplier` ADD COLUMN `duration_value` int NOT NULL DEFAULT 1 COMMENT ''时长数值'' AFTER `duration_unit`',
'SELECT 1'
);
PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
UPDATE `xxk_product_duration_multiplier`
SET `duration_unit` = 'DAY',
`duration_value` = `duration_days`
WHERE (`duration_unit` IS NULL OR `duration_unit` = '' OR `duration_unit` = 'DAY')
AND (`duration_value` IS NULL OR `duration_value` <= 1);
SET @index_exists = (
SELECT COUNT(1)
FROM information_schema.STATISTICS
WHERE TABLE_SCHEMA = @schema_name
AND TABLE_NAME = 'xxk_product_duration_multiplier'
AND INDEX_NAME = 'uk_product_duration'
);
SET @sql = IF(
@index_exists > 0,
'ALTER TABLE `xxk_product_duration_multiplier` DROP INDEX `uk_product_duration`',
'SELECT 1'
);
PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
SET @index_exists = (
SELECT COUNT(1)
FROM information_schema.STATISTICS
WHERE TABLE_SCHEMA = @schema_name
AND TABLE_NAME = 'xxk_product_duration_multiplier'
AND INDEX_NAME = 'uk_product_duration_unit_value'
);
SET @sql = IF(
@index_exists = 0,
'ALTER TABLE `xxk_product_duration_multiplier` ADD UNIQUE KEY `uk_product_duration_unit_value` (`product_id`, `duration_unit`, `duration_value`) USING BTREE',
'SELECT 1'
);
PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
SET @index_exists = (
SELECT COUNT(1)
FROM information_schema.STATISTICS
WHERE TABLE_SCHEMA = @schema_name
AND TABLE_NAME = 'xxk_product_duration_multiplier'
AND INDEX_NAME = 'idx_product_duration_days'
);
SET @sql = IF(
@index_exists = 0,
'ALTER TABLE `xxk_product_duration_multiplier` ADD INDEX `idx_product_duration_days` (`product_id`, `duration_days`) USING BTREE',
'SELECT 1'
);
PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
SET @column_exists = (SELECT COUNT(1) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = @schema_name AND TABLE_NAME = 'xxk_product_static_country_price' AND COLUMN_NAME = 'price_type');
SET @sql = IF(@column_exists = 0, 'ALTER TABLE `xxk_product_static_country_price` ADD COLUMN `price_type` varchar(20) DEFAULT NULL COMMENT ''价格类型(DEFAULT默认/NODE特殊节点)'' AFTER `product_id`', 'SELECT 1');
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
SET @column_exists = (SELECT COUNT(1) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = @schema_name AND TABLE_NAME = 'xxk_product_static_country_price' AND COLUMN_NAME = 'qiyun_product_type');
SET @sql = IF(@column_exists = 0, 'ALTER TABLE `xxk_product_static_country_price` ADD COLUMN `qiyun_product_type` varchar(32) DEFAULT NULL COMMENT ''齐云产品类型'' AFTER `price_type`', 'SELECT 1');
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
SET @column_exists = (SELECT COUNT(1) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = @schema_name AND TABLE_NAME = 'xxk_product_static_country_price' AND COLUMN_NAME = 'qiyun_pid');
SET @sql = IF(@column_exists = 0, 'ALTER TABLE `xxk_product_static_country_price` ADD COLUMN `qiyun_pid` varchar(64) DEFAULT NULL COMMENT ''齐云项目ID'' AFTER `qiyun_product_type`', 'SELECT 1');
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
SET @column_exists = (SELECT COUNT(1) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = @schema_name AND TABLE_NAME = 'xxk_product_static_country_price' AND COLUMN_NAME = 'qiyun_project_name');
SET @sql = IF(@column_exists = 0, 'ALTER TABLE `xxk_product_static_country_price` ADD COLUMN `qiyun_project_name` varchar(255) DEFAULT NULL COMMENT ''齐云项目名称'' AFTER `qiyun_pid`', 'SELECT 1');
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
SET @column_exists = (SELECT COUNT(1) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = @schema_name AND TABLE_NAME = 'xxk_product_static_country_price' AND COLUMN_NAME = 'qiyun_area_id');
SET @sql = IF(@column_exists = 0, 'ALTER TABLE `xxk_product_static_country_price` ADD COLUMN `qiyun_area_id` varchar(64) DEFAULT NULL COMMENT ''齐云省份ID'' AFTER `qiyun_project_name`', 'SELECT 1');
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
SET @column_exists = (SELECT COUNT(1) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = @schema_name AND TABLE_NAME = 'xxk_product_static_country_price' AND COLUMN_NAME = 'qiyun_area_name');
SET @sql = IF(@column_exists = 0, 'ALTER TABLE `xxk_product_static_country_price` ADD COLUMN `qiyun_area_name` varchar(255) DEFAULT NULL COMMENT ''齐云省份名称'' AFTER `qiyun_area_id`', 'SELECT 1');
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
SET @column_exists = (SELECT COUNT(1) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = @schema_name AND TABLE_NAME = 'xxk_product_static_country_price' AND COLUMN_NAME = 'qiyun_node_id');
SET @sql = IF(@column_exists = 0, 'ALTER TABLE `xxk_product_static_country_price` ADD COLUMN `qiyun_node_id` varchar(100) DEFAULT NULL COMMENT ''齐云节点ID'' AFTER `qiyun_area_name`', 'SELECT 1');
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
SET @column_exists = (SELECT COUNT(1) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = @schema_name AND TABLE_NAME = 'xxk_product_static_country_price' AND COLUMN_NAME = 'qiyun_node_name');
SET @sql = IF(@column_exists = 0, 'ALTER TABLE `xxk_product_static_country_price` ADD COLUMN `qiyun_node_name` varchar(255) DEFAULT NULL COMMENT ''齐云节点名称'' AFTER `qiyun_node_id`', 'SELECT 1');
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
ALTER TABLE `xxk_product` MODIFY COLUMN `currency` varchar(10) DEFAULT 'RMB' COMMENT '币种';
ALTER TABLE `xxk_product_static_country_price` MODIFY COLUMN `currency` varchar(10) DEFAULT 'RMB' COMMENT '币种';
ALTER TABLE `xxk_proxy_order` MODIFY COLUMN `currency` varchar(10) DEFAULT 'RMB' COMMENT '币种';
UPDATE `xxk_product` SET `currency` = 'RMB' WHERE `currency` IS NULL OR `currency` = '' OR `currency` = 'USD';
UPDATE `xxk_product_static_country_price` SET `currency` = 'RMB' WHERE `currency` IS NULL OR `currency` = '' OR `currency` = 'USD';
UPDATE `xxk_proxy_order` SET `currency` = 'RMB' WHERE `currency` IS NULL OR `currency` = '' OR `currency` = 'USD';
SET @index_exists = (
SELECT COUNT(1)
FROM information_schema.STATISTICS
WHERE TABLE_SCHEMA = @schema_name
AND TABLE_NAME = 'xxk_product_static_country_price'
AND INDEX_NAME = 'uk_product_country'
);
SET @sql = IF(
@index_exists > 0,
'ALTER TABLE `xxk_product_static_country_price` DROP INDEX `uk_product_country`',
'SELECT 1'
);
PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
SET @index_exists = (
SELECT COUNT(1)
FROM information_schema.STATISTICS
WHERE TABLE_SCHEMA = @schema_name
AND TABLE_NAME = 'xxk_product_static_country_price'
AND INDEX_NAME = 'idx_product_country'
);
SET @sql = IF(
@index_exists = 0,
'ALTER TABLE `xxk_product_static_country_price` ADD INDEX `idx_product_country` (`product_id`, `country_code`) USING BTREE',
'SELECT 1'
);
PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
SET @index_exists = (
SELECT COUNT(1)
FROM information_schema.STATISTICS
WHERE TABLE_SCHEMA = @schema_name
AND TABLE_NAME = 'xxk_product_static_country_price'
AND INDEX_NAME = 'idx_static_price_qiyun_node'
);
SET @sql = IF(
@index_exists = 0,
'ALTER TABLE `xxk_product_static_country_price` ADD INDEX `idx_static_price_qiyun_node` (`product_id`, `price_type`, `qiyun_node_id`) USING BTREE',
'SELECT 1'
);
PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
UPDATE `xxk_upstream_provider`
SET `provider_code` = 'QIYUN01',
`provider_name` = '齐云IP',
`provider_type` = 'QIYUN',
`base_url` = 'https://www.qiyunip.com',
`auth_user_id` = NULL,
`auth_token` = NULL,
`success_codes` = '1'
WHERE `provider_type` = 'IPNUX';
-- 开放 API 模块补丁:应用、申请、独立账户、充值、回调日志和后台菜单。
CREATE TABLE IF NOT EXISTS `xxk_open_api_app` (
`id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`app_id` VARCHAR(64) NOT NULL COMMENT '应用ID',
`app_name` VARCHAR(128) NOT NULL COMMENT '应用名称',
`app_secret` VARCHAR(128) NOT NULL COMMENT '应用密钥',
`member_user_id` BIGINT NOT NULL COMMENT '绑定会员ID',
`status` TINYINT NOT NULL DEFAULT 1 COMMENT '状态(0停用 1启用)',
`allow_ip_list` TEXT DEFAULT NULL COMMENT '允许访问IP列表,逗号/换行分隔',
`callback_url` VARCHAR(255) DEFAULT NULL COMMENT '订单结果回调地址',
`callback_secret` VARCHAR(128) DEFAULT NULL COMMENT '订单结果回调签名密钥',
`last_auth_time` DATETIME DEFAULT NULL COMMENT '最近换取token时间',
`last_auth_ip` VARCHAR(64) DEFAULT NULL COMMENT '最近换取token IP',
`remark` VARCHAR(255) DEFAULT NULL COMMENT '备注',
`create_by` BIGINT DEFAULT NULL COMMENT '创建人ID',
`update_by` BIGINT DEFAULT NULL COMMENT '更新人ID',
`create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`is_deleted` TINYINT NOT NULL DEFAULT 0 COMMENT '是否删除(0否 1是)',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_xxk_open_api_app_app_id` (`app_id`),
UNIQUE KEY `uk_xxk_open_api_app_member_user_id` (`member_user_id`),
KEY `idx_xxk_open_api_app_member_user_id` (`member_user_id`),
KEY `idx_xxk_open_api_app_status` (`status`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='开放接口应用表';
SET @column_exists = (
SELECT COUNT(1)
FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = @schema_name
AND TABLE_NAME = 'xxk_open_api_app'
AND COLUMN_NAME = 'callback_url'
);
SET @sql = IF(
@column_exists = 0,
'ALTER TABLE `xxk_open_api_app` ADD COLUMN `callback_url` VARCHAR(255) DEFAULT NULL COMMENT ''订单结果回调地址'' AFTER `allow_ip_list`',
'SELECT 1'
);
PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
SET @column_exists = (
SELECT COUNT(1)
FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = @schema_name
AND TABLE_NAME = 'xxk_open_api_app'
AND COLUMN_NAME = 'callback_secret'
);
SET @sql = IF(
@column_exists = 0,
'ALTER TABLE `xxk_open_api_app` ADD COLUMN `callback_secret` VARCHAR(128) DEFAULT NULL COMMENT ''订单结果回调签名密钥'' AFTER `callback_url`',
'SELECT 1'
);
PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
CREATE TABLE IF NOT EXISTS `xxk_open_api_apply` (
`id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`apply_no` VARCHAR(64) NOT NULL COMMENT '申请单号',
`member_user_id` BIGINT NOT NULL COMMENT '会员ID',
`username_snapshot` VARCHAR(64) DEFAULT NULL COMMENT '用户名快照',
`mobile_snapshot` VARCHAR(32) DEFAULT NULL COMMENT '手机号快照',
`contact_name` VARCHAR(64) NOT NULL COMMENT '联系人',
`contact_mobile` VARCHAR(32) DEFAULT NULL COMMENT '联系电话',
`contact_email` VARCHAR(128) DEFAULT NULL COMMENT '联系邮箱',
`company_name` VARCHAR(128) DEFAULT NULL COMMENT '公司名称',
`purpose` VARCHAR(255) NOT NULL COMMENT '申请用途',
`scenario_description` TEXT NOT NULL COMMENT '使用场景说明',
`allow_ip_list` TEXT DEFAULT NULL COMMENT 'IP白名单',
`status` TINYINT NOT NULL DEFAULT 0 COMMENT '状态(0待审核 1已通过 2已驳回)',
`submit_remark` VARCHAR(255) DEFAULT NULL COMMENT '补充说明',
`submit_time` DATETIME DEFAULT NULL COMMENT '提交时间',
`audit_time` DATETIME DEFAULT NULL COMMENT '审核时间',
`audit_by` BIGINT DEFAULT NULL COMMENT '审核人ID',
`audit_remark` VARCHAR(255) DEFAULT NULL COMMENT '审核备注',
`open_api_app_id` BIGINT DEFAULT NULL COMMENT '关联开放应用ID',
`create_by` BIGINT DEFAULT NULL COMMENT '创建人ID',
`update_by` BIGINT DEFAULT NULL COMMENT '更新人ID',
`create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`is_deleted` TINYINT NOT NULL DEFAULT 0 COMMENT '是否删除(0否 1是)',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_xxk_open_api_apply_no` (`apply_no`),
KEY `idx_xxk_open_api_apply_member_user_id` (`member_user_id`),
KEY `idx_xxk_open_api_apply_status` (`status`),
KEY `idx_xxk_open_api_apply_submit_time` (`submit_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='开放接口申请表';
CREATE TABLE IF NOT EXISTS `xxk_open_api_account` (
`id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`open_api_app_id` BIGINT NOT NULL COMMENT '开放应用ID',
`member_user_id` BIGINT NOT NULL COMMENT '会员ID',
`balance` DECIMAL(18,2) NOT NULL DEFAULT 0 COMMENT '余额',
`frozen_balance` DECIMAL(18,2) NOT NULL DEFAULT 0 COMMENT '冻结余额',
`total_recharge_amount` DECIMAL(18,2) NOT NULL DEFAULT 0 COMMENT '累计充值金额',
`total_consume_amount` DECIMAL(18,2) NOT NULL DEFAULT 0 COMMENT '累计消费金额',
`total_refund_amount` DECIMAL(18,2) NOT NULL DEFAULT 0 COMMENT '累计退款金额',
`status` TINYINT NOT NULL DEFAULT 1 COMMENT '状态(0停用 1正常)',
`create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_xxk_open_api_account_app_id` (`open_api_app_id`),
KEY `idx_xxk_open_api_account_member_user_id` (`member_user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='API独立账户表';
CREATE TABLE IF NOT EXISTS `xxk_open_api_account_flow` (
`id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`flow_no` VARCHAR(64) NOT NULL COMMENT '流水号',
`open_api_app_id` BIGINT NOT NULL COMMENT '开放应用ID',
`member_user_id` BIGINT NOT NULL COMMENT '会员ID',
`biz_type` VARCHAR(64) NOT NULL COMMENT '业务类型',
`change_type` VARCHAR(16) NOT NULL COMMENT '变动类型(IN/OUT)',
`change_amount` DECIMAL(18,2) NOT NULL DEFAULT 0 COMMENT '变动金额',
`before_balance` DECIMAL(18,2) NOT NULL DEFAULT 0 COMMENT '变动前余额',
`after_balance` DECIMAL(18,2) NOT NULL DEFAULT 0 COMMENT '变动后余额',
`related_order_no` VARCHAR(64) DEFAULT NULL COMMENT '关联单号',
`remark` VARCHAR(255) DEFAULT NULL COMMENT '备注',
`operate_by` BIGINT DEFAULT NULL COMMENT '操作人ID',
`create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_xxk_open_api_account_flow_no` (`flow_no`),
KEY `idx_xxk_open_api_account_flow_app_id` (`open_api_app_id`),
KEY `idx_xxk_open_api_account_flow_related_order_no` (`related_order_no`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='API独立账户流水表';
CREATE TABLE IF NOT EXISTS `xxk_open_api_recharge_order` (
`id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`recharge_no` VARCHAR(64) NOT NULL COMMENT '充值单号',
`pay_order_no` VARCHAR(64) NOT NULL COMMENT '支付单号',
`open_api_app_id` BIGINT NOT NULL COMMENT '开放应用ID',
`member_user_id` BIGINT NOT NULL COMMENT '会员ID',
`payment_type` VARCHAR(32) NOT NULL COMMENT '支付方式',
`pay_status` VARCHAR(32) NOT NULL COMMENT '支付状态',
`channel_order_no` VARCHAR(64) DEFAULT NULL COMMENT '渠道订单号',
`channel_response` LONGTEXT DEFAULT NULL COMMENT '渠道响应',
`client_type` VARCHAR(32) DEFAULT NULL COMMENT '客户端类型',
`return_url` VARCHAR(500) DEFAULT NULL COMMENT '回跳地址',
`amount` DECIMAL(18,2) NOT NULL DEFAULT 0 COMMENT '充值金额',
`gift_amount` DECIMAL(18,2) NOT NULL DEFAULT 0 COMMENT '赠送金额',
`credited_amount` DECIMAL(18,2) NOT NULL DEFAULT 0 COMMENT '到账金额',
`promotion_rule_snapshot` TEXT DEFAULT NULL COMMENT '活动规则快照',
`currency` VARCHAR(16) DEFAULT 'USD' COMMENT '币种',
`paid_time` DATETIME DEFAULT NULL COMMENT '支付完成时间',
`remark` VARCHAR(255) DEFAULT NULL COMMENT '备注',
`is_deleted` TINYINT NOT NULL DEFAULT 0 COMMENT '是否删除(0否 1是)',
`create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_xxk_open_api_recharge_no` (`recharge_no`),
UNIQUE KEY `uk_xxk_open_api_pay_order_no` (`pay_order_no`),
KEY `idx_xxk_open_api_recharge_order_app_id` (`open_api_app_id`),
KEY `idx_xxk_open_api_recharge_order_member_user_id` (`member_user_id`),
KEY `idx_xxk_open_api_recharge_order_pay_status` (`pay_status`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='API独立账户充值单表';
CREATE TABLE IF NOT EXISTS `xxk_open_api_callback_log` (
`id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`open_api_app_id` BIGINT NOT NULL COMMENT '开放应用ID',
`member_user_id` BIGINT NOT NULL COMMENT '会员ID',
`order_id` BIGINT NOT NULL COMMENT '订单ID',
`order_no` VARCHAR(64) NOT NULL COMMENT '订单号',
`callback_url` VARCHAR(255) DEFAULT NULL COMMENT '回调地址',
`event_type` VARCHAR(64) NOT NULL COMMENT '事件类型',
`request_body` LONGTEXT DEFAULT NULL COMMENT '请求报文',
`response_body` LONGTEXT DEFAULT NULL COMMENT '响应报文',
`response_status` INT DEFAULT NULL COMMENT '响应HTTP状态码',
`delivery_status` VARCHAR(32) NOT NULL DEFAULT 'PENDING' COMMENT '投递状态(PENDING/SUCCESS/FAIL/SKIPPED)',
`error_message` VARCHAR(500) DEFAULT NULL COMMENT '错误信息',
`attempt_no` INT NOT NULL DEFAULT 1 COMMENT '尝试次数',
`trigger_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '触发时间',
`finish_time` DATETIME DEFAULT NULL COMMENT '完成时间',
`create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`),
KEY `idx_xxk_open_api_callback_log_app_id` (`open_api_app_id`),
KEY `idx_xxk_open_api_callback_log_order_no` (`order_no`),
KEY `idx_xxk_open_api_callback_log_status` (`delivery_status`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='开放API订单回调日志表';
INSERT INTO `sys_menu`
(`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`,
`always_show`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`)
VALUES
(1220, 1100, '0,1100', '开放API管理', 'C', NULL, '/open-api-manage', 'Layout', NULL,
1, 0, 1, 13, 'api', '/open-api-manage/open-api', NOW(), NOW(), NULL),
(1221, 1220, '0,1100,1220', '开放API申请审核', 'M', 'ProxyOpenApiApply', 'open-api', 'proxy/open-api/index', NULL,
0, 1, 1, 1, 'form', NULL, NOW(), NOW(), NULL),
(122101, 1221, '0,1100,1220,1221', '开放API申请查询', 'B', NULL, '', NULL, 'openapi:apply:list',
NULL, NULL, 1, 1, '', NULL, NOW(), NOW(), NULL),
(122102, 1221, '0,1100,1220,1221', '开放API申请审核', 'B', NULL, '', NULL, 'openapi:apply:audit',
NULL, NULL, 1, 2, '', NULL, NOW(), NOW(), NULL),
(1222, 1220, '0,1100,1220', '开放API回调日志', 'M', 'ProxyOpenApiCallback', 'open-api-callback', 'proxy/open-api-callback/index', NULL,
0, 1, 1, 2, 'notification', NULL, NOW(), NOW(), NULL),
(122201, 1222, '0,1100,1220,1222', '开放API回调日志查询', 'B', NULL, '', NULL, 'openapi:callback-log:list',
NULL, NULL, 1, 1, '', NULL, NOW(), NOW(), NULL),
(122202, 1222, '0,1100,1220,1222', '开放API回调日志重发', 'B', NULL, '', NULL, 'openapi:callback-log:retry',
NULL, NULL, 1, 2, '', NULL, NOW(), NOW(), NULL),
(1223, 1220, '0,1100,1220', '开放API账户中心', 'M', 'ProxyOpenApiAccount', 'open-api-account', 'proxy/open-api-account/index', NULL,
0, 1, 0, 3, 'wallet', NULL, NOW(), NOW(), NULL)
ON DUPLICATE KEY UPDATE
`name` = VALUES(`name`),
`route_name` = VALUES(`route_name`),
`route_path` = VALUES(`route_path`),
`component` = VALUES(`component`),
`perm` = VALUES(`perm`),
`keep_alive` = VALUES(`keep_alive`),
`visible` = VALUES(`visible`),
`sort` = VALUES(`sort`),
`icon` = VALUES(`icon`),
`update_time` = NOW();
INSERT IGNORE INTO `sys_role_menu` (`role_id`, `menu_id`) VALUES
(2, 1220), (2, 1221), (2, 122101), (2, 122102), (2, 1222), (2, 122201), (2, 122202), (2, 1223);
@@ -0,0 +1,26 @@
ALTER TABLE `xxk_proxy_city_library`
RENAME TO `xxk_proxy_region`;
ALTER TABLE `xxk_proxy_region`
CHANGE COLUMN `city_code` `region_code` varchar(64) NOT NULL COMMENT '地区编号',
CHANGE COLUMN `city_name_zh` `region_name_zh` varchar(100) NOT NULL COMMENT '地区中文名',
CHANGE COLUMN `city_name_en` `region_name` varchar(100) NOT NULL COMMENT '地区名称',
CHANGE COLUMN `icon_url` `icon_url` varchar(500) NOT NULL COMMENT '地区图标',
DROP COLUMN `country_code`,
DROP COLUMN `country_name`;
ALTER TABLE `xxk_product_static_country_price`
ADD COLUMN `region_id` bigint DEFAULT NULL COMMENT '地区ID' AFTER `product_id`,
ADD COLUMN `region_code` varchar(64) DEFAULT NULL COMMENT '地区编号' AFTER `region_id`,
ADD COLUMN `region_name` varchar(100) DEFAULT NULL COMMENT '地区名称' AFTER `region_code`,
ADD COLUMN `region_name_zh` varchar(100) DEFAULT NULL COMMENT '地区中文名' AFTER `region_name`;
UPDATE `xxk_product_static_country_price`
SET
`region_code` = `country_code`,
`region_name` = IFNULL(`region_name`, `country_name`),
`region_name_zh` = IFNULL(`region_name_zh`, `country_name`)
WHERE `region_code` IS NULL OR `region_name_zh` IS NULL;
ALTER TABLE `xxk_product_static_country_price`
ADD KEY `idx_static_price_region_id` (`region_id`);
+38
View File
@@ -0,0 +1,38 @@
USE youlai_admin;
-- 代理平台主管演示角色
SET @proxy_role_code = 'PROXY_MANAGER';
INSERT INTO `sys_role`
(`name`, `code`, `sort`, `status`, `data_scope`, `create_by`, `create_time`, `update_by`, `update_time`, `is_deleted`)
SELECT '代理平台主管', @proxy_role_code, 80, 1, 1, 1, now(), 1, now(), 0
FROM DUAL
WHERE NOT EXISTS (
SELECT 1
FROM `sys_role`
WHERE `code` = @proxy_role_code
);
SET @proxy_role_id = (
SELECT `id`
FROM `sys_role`
WHERE `code` = @proxy_role_code
LIMIT 1
);
INSERT IGNORE INTO `sys_role_menu` (`role_id`, `menu_id`)
SELECT @proxy_role_id, `id`
FROM `sys_menu`
WHERE `id` IN (
1100,
1110, 11101, 11102, 11103, 11104,
1120, 11201, 11202, 11203, 11204,
1130, 11301, 11302, 11303, 11304,
1140, 11401, 11402, 11403, 11404,
1150, 11501, 11502, 11503,
1160, 11601, 11602, 11603,
1170, 11701, 11702,
1180, 11801, 11802, 11803, 11804,
1190, 11901, 11902,
1200, 12001, 12002, 12003
);
@@ -0,0 +1,29 @@
USE youlai_admin;
-- 上游请求日志菜单升级脚本
-- 说明:
-- 1. 在代理平台下新增“上游请求日志”菜单。
-- 2. 用于查看上游代理平台请求与响应日志,辅助联调和失败排查。
INSERT INTO `sys_menu`
(`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`,
`always_show`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`)
VALUES
(1185, 1100, '0,1100', '上游请求日志', 'M', 'ProxyUpstreamRequestLog', 'upstream-request-log', 'proxy/upstream-request-log/index', NULL,
0, 1, 1, 11, 'histogram', NULL, NOW(), NOW(), NULL),
(11851, 1185, '0,1100,1185', '上游请求日志查询', 'B', NULL, '', NULL, 'proxy:upstream-request-log:list',
NULL, NULL, 1, 1, '', NULL, NOW(), NOW(), NULL)
ON DUPLICATE KEY UPDATE
`name` = VALUES(`name`),
`route_name` = VALUES(`route_name`),
`route_path` = VALUES(`route_path`),
`component` = VALUES(`component`),
`perm` = VALUES(`perm`),
`keep_alive` = VALUES(`keep_alive`),
`visible` = VALUES(`visible`),
`sort` = VALUES(`sort`),
`icon` = VALUES(`icon`),
`update_time` = NOW();
INSERT IGNORE INTO `sys_role_menu` (`role_id`, `menu_id`) VALUES
(2, 1185), (2, 11851);
@@ -0,0 +1,9 @@
USE youlai_admin;
-- 钱包流水表 update_time 字段补齐脚本
-- 说明:
-- 1. 当前代码中的钱包流水实体继承了 BaseEntity,插入时会自动写入 update_time。
-- 2. 部分旧库的 xxk_wallet_flow 表缺少 update_time,导致支付、退款等钱包流水写入时报错。
ALTER TABLE `xxk_wallet_flow`
ADD COLUMN `update_time` DATETIME NULL COMMENT '更新时间' AFTER `create_time`;
@@ -0,0 +1,22 @@
USE youlai_admin;
-- 钱包充值订单管理菜单
INSERT INTO `sys_menu`
(`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`,
`always_show`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`)
VALUES
(11910, 1190, '0,1100,1190', '充值订单', 'C', 'ProxyWalletRecharge', 'wallet-recharge', 'proxy/wallet-recharge/index', NULL,
0, 1, 1, 3, 'wallet', NULL, now(), now(), NULL),
(11911, 11910, '0,1100,1190,11910', '充值订单查询', 'B', NULL, '', NULL, 'proxy:wallet:recharge-order:list',
NULL, NULL, 1, 1, '', NULL, now(), now(), NULL)
ON DUPLICATE KEY UPDATE
`name` = VALUES(`name`),
`route_name` = VALUES(`route_name`),
`route_path` = VALUES(`route_path`),
`component` = VALUES(`component`),
`perm` = VALUES(`perm`),
`keep_alive` = VALUES(`keep_alive`),
`visible` = VALUES(`visible`),
`sort` = VALUES(`sort`),
`update_time` = now();
@@ -0,0 +1,50 @@
-- 钱包在线充值单升级脚本
-- 说明:
-- 1. 新增会员钱包在线充值单,独立承接 DaxPay 支付链路。
-- 2. 钱包入账仍然落到 xxk_wallet_account / xxk_wallet_flow。
CREATE TABLE IF NOT EXISTS `xxk_wallet_recharge_order` (
`id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`recharge_no` VARCHAR(64) NOT NULL COMMENT '充值单号',
`pay_order_no` VARCHAR(64) NOT NULL COMMENT '支付单号',
`member_user_id` BIGINT NOT NULL COMMENT '会员ID',
`payment_type` VARCHAR(32) NOT NULL COMMENT '支付方式(ALIPAY/WECHAT)',
`pay_status` VARCHAR(32) NOT NULL DEFAULT 'INIT' COMMENT '支付状态(INIT/PAYING/PAID/FAIL/CLOSED)',
`channel_order_no` VARCHAR(128) DEFAULT NULL COMMENT '渠道支付单号',
`channel_response` TEXT DEFAULT NULL COMMENT '渠道原始响应',
`client_type` VARCHAR(32) DEFAULT NULL COMMENT '客户端类型(PC/H5/APP/MINI_PROGRAM)',
`return_url` VARCHAR(500) DEFAULT NULL COMMENT '支付完成跳转地址',
`amount` DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '充值金额',
`currency` VARCHAR(16) DEFAULT 'CNY' COMMENT '币种',
`paid_time` DATETIME DEFAULT NULL COMMENT '支付成功时间',
`remark` VARCHAR(255) DEFAULT NULL COMMENT '备注',
`create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`is_deleted` TINYINT NOT NULL DEFAULT 0 COMMENT '是否删除(0否 1是)',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_xxk_wallet_recharge_no` (`recharge_no`),
UNIQUE KEY `uk_xxk_wallet_recharge_pay_order_no` (`pay_order_no`),
KEY `idx_xxk_wallet_recharge_member_status` (`member_user_id`, `pay_status`, `create_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='钱包在线充值单';
-- 钱包充值订单管理菜单
INSERT INTO `sys_menu`
(`id`, `parent_id`, `tree_path`, `name`, `type`, `route_name`, `route_path`, `component`, `perm`,
`always_show`, `keep_alive`, `visible`, `sort`, `icon`, `redirect`, `create_time`, `update_time`, `params`)
VALUES
(11910, 1190, '0,1100,1190', '充值订单', 'C', 'ProxyWalletRecharge', 'wallet-recharge', 'proxy/wallet-recharge/index', NULL,
0, 1, 1, 3, 'wallet', NULL, now(), now(), NULL),
(11911, 11910, '0,1100,1190,11910', '充值订单查询', 'B', NULL, '', NULL, 'proxy:wallet:recharge-order:list',
NULL, NULL, 1, 1, '', NULL, now(), now(), NULL)
ON DUPLICATE KEY UPDATE
`name` = VALUES(`name`),
`route_name` = VALUES(`route_name`),
`route_path` = VALUES(`route_path`),
`component` = VALUES(`component`),
`perm` = VALUES(`perm`),
`keep_alive` = VALUES(`keep_alive`),
`visible` = VALUES(`visible`),
`sort` = VALUES(`sort`),
`update_time` = now();
+586
View File
@@ -0,0 +1,586 @@
# YouLai_Admin (MySQL 5.7 ~ MySQL 8.x)
# Copyright (c) 2021-present, youlai.tech
-- ----------------------------
-- 1. 创建数据库
-- ----------------------------
CREATE DATABASE IF NOT EXISTS youlai_admin CHARACTER SET utf8mb4 DEFAULT COLLATE utf8mb4_unicode_ci;
-- ----------------------------
-- 2. 创建表 && 数据初始化
-- ----------------------------
USE youlai_admin;
SET NAMES utf8mb4; #
SET FOREIGN_KEY_CHECKS = 0; #
-- ----------------------------
-- Table structure for sys_dept
-- ----------------------------
DROP TABLE IF EXISTS `sys_dept`;
CREATE TABLE `sys_dept` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键',
`name` varchar(100) NOT NULL COMMENT '部门名称',
`code` varchar(100) NOT NULL COMMENT '部门编号',
`parent_id` bigint DEFAULT 0 COMMENT '父节点id',
`tree_path` varchar(255) NOT NULL COMMENT '父节点id路径',
`sort` smallint DEFAULT 0 COMMENT '显示顺序',
`status` tinyint DEFAULT 1 COMMENT '状态(1-正常 0-禁用)',
`create_by` bigint NULL COMMENT '创建人ID',
`create_time` datetime NULL COMMENT '创建时间',
`update_by` bigint NULL COMMENT '修改人ID',
`update_time` datetime NULL COMMENT '更新时间',
`is_deleted` tinyint DEFAULT 0 COMMENT '逻辑删除标识(1-已删除 0-未删除)',
PRIMARY KEY (`id`) USING BTREE,
UNIQUE INDEX `uk_code`(`code` ASC) USING BTREE COMMENT '部门编号唯一索引'
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COMMENT = '部门管理表';
-- ----------------------------
-- Records of sys_dept
-- ----------------------------
INSERT INTO `sys_dept` VALUES (1, '有来技术', 'YOULAI', 0, '0', 1, 1, 1, NULL, 1, now(), 0);
INSERT INTO `sys_dept` VALUES (2, '研发部门', 'RD001', 1, '0,1', 1, 1, 2, NULL, 2, now(), 0);
INSERT INTO `sys_dept` VALUES (3, '测试部门', 'QA001', 1, '0,1', 1, 1, 2, NULL, 2, now(), 0);
-- ----------------------------
-- Table structure for sys_dict
-- ----------------------------
DROP TABLE IF EXISTS `sys_dict`;
CREATE TABLE `sys_dict` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键 ',
`dict_code` varchar(50) COMMENT '类型编码',
`name` varchar(50) COMMENT '类型名称',
`status` tinyint(1) DEFAULT '0' COMMENT '状态(0:正常;1:禁用)',
`remark` varchar(255) COMMENT '备注',
`create_time` datetime COMMENT '创建时间',
`create_by` bigint COMMENT '创建人ID',
`update_time` datetime COMMENT '更新时间',
`update_by` bigint COMMENT '修改人ID',
`is_deleted` tinyint DEFAULT '0' COMMENT '是否删除(1-删除,0-未删除)',
PRIMARY KEY (`id`) USING BTREE,
KEY `idx_dict_code` (`dict_code`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='数据字典类型表';
-- ----------------------------
-- Records of sys_dict
-- ----------------------------
INSERT INTO `sys_dict` VALUES (1, 'gender', '性别', 1, NULL, now() , 1,now(), 1,0);
INSERT INTO `sys_dict` VALUES (2, 'notice_type', '通知类型', 1, NULL, now(), 1,now(), 1,0);
INSERT INTO `sys_dict` VALUES (3, 'notice_level', '通知级别', 1, NULL, now(), 1,now(), 1,0);
-- ----------------------------
-- Table structure for sys_dict_item
-- ----------------------------
DROP TABLE IF EXISTS `sys_dict_item`;
CREATE TABLE `sys_dict_item` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键',
`dict_code` varchar(50) COMMENT '关联字典编码,与sys_dict表中的dict_code对应',
`value` varchar(50) COMMENT '字典项值',
`label` varchar(100) COMMENT '字典项标签',
`tag_type` varchar(50) COMMENT '标签类型,用于前端样式展示(如success、warning等)',
`status` tinyint DEFAULT '0' COMMENT '状态(1-正常,0-禁用)',
`sort` int DEFAULT '0' COMMENT '排序',
`remark` varchar(255) COMMENT '备注',
`create_time` datetime COMMENT '创建时间',
`create_by` bigint COMMENT '创建人ID',
`update_time` datetime COMMENT '更新时间',
`update_by` bigint COMMENT '修改人ID',
PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='数据字典项表';
-- ----------------------------
-- Records of sys_dict_item
-- ----------------------------
INSERT INTO `sys_dict_item` VALUES (1, 'gender', '1', '', 'primary', 1, 1, NULL, now(), 1,now(),1);
INSERT INTO `sys_dict_item` VALUES (2, 'gender', '2', '', 'danger', 1, 2, NULL, now(), 1,now(),1);
INSERT INTO `sys_dict_item` VALUES (3, 'gender', '0', '保密', 'info', 1, 3, NULL, now(), 1,now(),1);
INSERT INTO `sys_dict_item` VALUES (4, 'notice_type', '1', '系统升级', 'success', 1, 1, '', now(), 1,now(),1);
INSERT INTO `sys_dict_item` VALUES (5, 'notice_type', '2', '系统维护', 'primary', 1, 2, '', now(), 1,now(),1);
INSERT INTO `sys_dict_item` VALUES (6, 'notice_type', '3', '安全警告', 'danger', 1, 3, '', now(), 1,now(),1);
INSERT INTO `sys_dict_item` VALUES (7, 'notice_type', '4', '假期通知', 'success', 1, 4, '', now(), 1,now(),1);
INSERT INTO `sys_dict_item` VALUES (8, 'notice_type', '5', '公司新闻', 'primary', 1, 5, '', now(), 1,now(),1);
INSERT INTO `sys_dict_item` VALUES (9, 'notice_type', '99', '其他', 'info', 1, 99, '', now(), 1,now(),1);
INSERT INTO `sys_dict_item` VALUES (10, 'notice_level', 'L', '', 'info', 1, 1, '', now(), 1,now(),1);
INSERT INTO `sys_dict_item` VALUES (11, 'notice_level', 'M', '', 'warning', 1, 2, '', now(), 1,now(),1);
INSERT INTO `sys_dict_item` VALUES (12, 'notice_level', 'H', '', 'danger', 1, 3, '', now(), 1,now(),1);
-- ----------------------------
-- Table structure for sys_menu
-- ----------------------------
DROP TABLE IF EXISTS `sys_menu`;
CREATE TABLE `sys_menu` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT 'ID',
`parent_id` bigint NOT NULL COMMENT '父菜单ID',
`tree_path` varchar(255) COMMENT '父节点ID路径',
`name` varchar(64) NOT NULL COMMENT '菜单名称',
`type` char(1) NOT NULL COMMENT '菜单类型(C-目录 M-菜单 B-按钮)',
`route_name` varchar(255) COMMENT '路由名称(Vue Router 中用于命名路由)',
`route_path` varchar(128) COMMENT '路由路径(Vue Router 中定义的 URL 路径)',
`component` varchar(128) COMMENT '组件路径(组件页面完整路径,相对于 src/views/,缺省后缀 .vue',
`perm` varchar(128) COMMENT '【按钮】权限标识',
`always_show` tinyint DEFAULT 0 COMMENT '【目录】只有一个子路由是否始终显示(1-是 0-否)',
`keep_alive` tinyint DEFAULT 0 COMMENT '【菜单】是否开启页面缓存(1-是 0-否)',
`visible` tinyint(1) DEFAULT 1 COMMENT '显示状态(1-显示 0-隐藏)',
`sort` int DEFAULT 0 COMMENT '排序',
`icon` varchar(64) COMMENT '菜单图标',
`redirect` varchar(128) COMMENT '跳转路径',
`create_time` datetime NULL COMMENT '创建时间',
`update_time` datetime NULL COMMENT '更新时间',
`params` json NULL COMMENT '路由参数',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COMMENT = '系统菜单表';
-- ----------------------------
-- Records of sys_menu
-- ----------------------------
-- 顶级目录(1-9):系统/代码生成/文档/接口文档/组件/演示/多级/路由
INSERT INTO `sys_menu` VALUES (1, 0, '0', '系统管理', 'C', '', '/system', 'Layout', NULL, NULL, NULL, 1, 1, 'system', '/system/user', now(), now(), NULL);
INSERT INTO `sys_menu` VALUES (2, 0, '0', '代码生成', 'C', '', '/codegen', 'Layout', NULL, NULL, NULL, 1, 2, 'code', '/codegen/index', now(), now(), NULL);
INSERT INTO `sys_menu` VALUES (4, 0, '0', '平台文档', 'C', '', '/doc', 'Layout', NULL, NULL, NULL, 1, 4, 'document', '', now(), now(), NULL);
INSERT INTO `sys_menu` VALUES (5, 0, '0', '接口文档', 'C', '', '/api', 'Layout', NULL, NULL, NULL, 1, 5, 'api', '', now(), now(), NULL);
INSERT INTO `sys_menu` VALUES (6, 0, '0', '组件封装', 'C', '', '/component', 'Layout', NULL, NULL, NULL, 1, 6, 'menu', '', now(), now(), NULL);
INSERT INTO `sys_menu` VALUES (7, 0, '0', '功能演示', 'C', '', '/function', 'Layout', NULL, NULL, NULL, 1, 7, 'menu', '', now(), now(), NULL);
INSERT INTO `sys_menu` VALUES (8, 0, '0', '多级菜单', 'C', NULL, '/multi-level', 'Layout', NULL, 1, NULL, 1, 8, 'cascader', '', now(), now(), NULL);
INSERT INTO `sys_menu` VALUES (9, 0, '0', '路由参数', 'C', '', '/route-param', 'Layout', NULL, NULL, NULL, 1, 9, 'el-icon-ElementPlus', '', now(), now(), NULL);
-- 系统管理
INSERT INTO `sys_menu` VALUES (210, 1, '0,1', '用户管理', 'M', 'User', 'user', 'system/user/index', NULL, NULL, 1, 1, 1, 'el-icon-User', NULL, now(), now(), NULL);
INSERT INTO `sys_menu` VALUES (2101, 210, '0,1,210', '用户查询', 'B', NULL, '', NULL, 'sys:user:list', NULL, NULL, 1, 1, '', NULL, now(), now(), NULL);
INSERT INTO `sys_menu` VALUES (2102, 210, '0,1,210', '用户新增', 'B', NULL, '', NULL, 'sys:user:create', NULL, NULL, 1, 2, '', NULL, now(), now(), NULL);
INSERT INTO `sys_menu` VALUES (2103, 210, '0,1,210', '用户编辑', 'B', NULL, '', NULL, 'sys:user:update', NULL, NULL, 1, 3, '', NULL, now(), now(), NULL);
INSERT INTO `sys_menu` VALUES (2104, 210, '0,1,210', '用户删除', 'B', NULL, '', NULL, 'sys:user:delete', NULL, NULL, 1, 4, '', NULL, now(), now(), NULL);
INSERT INTO `sys_menu` VALUES (2105, 210, '0,1,210', '重置密码', 'B', NULL, '', NULL, 'sys:user:reset-password', NULL, NULL, 1, 5, '', NULL, now(), now(), NULL);
INSERT INTO `sys_menu` VALUES (2106, 210, '0,1,210', '用户导入', 'B', NULL, '', NULL, 'sys:user:import', NULL, NULL, 1, 6, '', NULL, now(), now(), NULL);
INSERT INTO `sys_menu` VALUES (2107, 210, '0,1,210', '用户导出', 'B', NULL, '', NULL, 'sys:user:export', NULL, NULL, 1, 7, '', NULL, now(), now(), NULL);
INSERT INTO `sys_menu` VALUES (220, 1, '0,1', '角色管理', 'M', 'Role', 'role', 'system/role/index', NULL, NULL, 1, 1, 2, 'role', NULL, now(), now(), NULL);
INSERT INTO `sys_menu` VALUES (2201, 220, '0,1,220', '角色查询', 'B', NULL, '', NULL, 'sys:role:list', NULL, NULL, 1, 1, '', NULL, now(), now(), NULL);
INSERT INTO `sys_menu` VALUES (2202, 220, '0,1,220', '角色新增', 'B', NULL, '', NULL, 'sys:role:create', NULL, NULL, 1, 2, '', NULL, now(), now(), NULL);
INSERT INTO `sys_menu` VALUES (2203, 220, '0,1,220', '角色编辑', 'B', NULL, '', NULL, 'sys:role:update', NULL, NULL, 1, 3, '', NULL, now(), now(), NULL);
INSERT INTO `sys_menu` VALUES (2204, 220, '0,1,220', '角色删除', 'B', NULL, '', NULL, 'sys:role:delete', NULL, NULL, 1, 4, '', NULL, now(), now(), NULL);
INSERT INTO `sys_menu` VALUES (2205, 220, '0,1,220', '角色分配权限', 'B', NULL, '', NULL, 'sys:role:assign', NULL, NULL, 1, 5, '', NULL, now(), now(), NULL);
INSERT INTO `sys_menu` VALUES (230, 1, '0,1', '菜单管理', 'M', 'SysMenu', 'menu', 'system/menu/index', NULL, NULL, 1, 1, 3, 'menu', NULL, now(), now(), NULL);
INSERT INTO `sys_menu` VALUES (2301, 230, '0,1,230', '菜单查询', 'B', NULL, '', NULL, 'sys:menu:list', NULL, NULL, 1, 1, '', NULL, now(), now(), NULL);
INSERT INTO `sys_menu` VALUES (2302, 230, '0,1,230', '菜单新增', 'B', NULL, '', NULL, 'sys:menu:create', NULL, NULL, 1, 2, '', NULL, now(), now(), NULL);
INSERT INTO `sys_menu` VALUES (2303, 230, '0,1,230', '菜单编辑', 'B', NULL, '', NULL, 'sys:menu:update', NULL, NULL, 1, 3, '', NULL, now(), now(), NULL);
INSERT INTO `sys_menu` VALUES (2304, 230, '0,1,230', '菜单删除', 'B', NULL, '', NULL, 'sys:menu:delete', NULL, NULL, 1, 4, '', NULL, now(), now(), NULL);
INSERT INTO `sys_menu` VALUES (240, 1, '0,1', '部门管理', 'M', 'Dept', 'dept', 'system/dept/index', NULL, NULL, 1, 1, 4, 'tree', NULL, now(), now(), NULL);
INSERT INTO `sys_menu` VALUES (2401, 240, '0,1,240', '部门查询', 'B', NULL, '', NULL, 'sys:dept:list', NULL, NULL, 1, 1, '', NULL, now(), now(), NULL);
INSERT INTO `sys_menu` VALUES (2402, 240, '0,1,240', '部门新增', 'B', NULL, '', NULL, 'sys:dept:create', NULL, NULL, 1, 2, '', NULL, now(), now(), NULL);
INSERT INTO `sys_menu` VALUES (2403, 240, '0,1,240', '部门编辑', 'B', NULL, '', NULL, 'sys:dept:update', NULL, NULL, 1, 3, '', NULL, now(), now(), NULL);
INSERT INTO `sys_menu` VALUES (2404, 240, '0,1,240', '部门删除', 'B', NULL, '', NULL, 'sys:dept:delete', NULL, NULL, 1, 4, '', NULL, now(), now(), NULL);
INSERT INTO `sys_menu` VALUES (250, 1, '0,1', '字典管理', 'M', 'Dict', 'dict', 'system/dict/index', NULL, NULL, 1, 1, 5, 'dict', NULL, now(), now(), NULL);
INSERT INTO `sys_menu` VALUES (2501, 250, '0,1,250', '字典查询', 'B', NULL, '', NULL, 'sys:dict:list', NULL, NULL, 1, 1, '', NULL, now(), now(), NULL);
INSERT INTO `sys_menu` VALUES (2502, 250, '0,1,250', '字典新增', 'B', NULL, '', NULL, 'sys:dict:create', NULL, NULL, 1, 2, '', NULL, now(), now(), NULL);
INSERT INTO `sys_menu` VALUES (2503, 250, '0,1,250', '字典编辑', 'B', NULL, '', NULL, 'sys:dict:update', NULL, NULL, 1, 3, '', NULL, now(), now(), NULL);
INSERT INTO `sys_menu` VALUES (2504, 250, '0,1,250', '字典删除', 'B', NULL, '', NULL, 'sys:dict:delete', NULL, NULL, 1, 4, '', NULL, now(), now(), NULL);
INSERT INTO `sys_menu` VALUES (251, 1, '0,1', '字典项', 'M', 'DictItem', 'dict-item', 'system/dict/dict-item', NULL, 0, 1, 0, 6, '', NULL, now(), now(), NULL);
INSERT INTO `sys_menu` VALUES (2511, 251, '0,1,251', '字典项查询', 'B', NULL, '', NULL, 'sys:dict-item:list', NULL, NULL, 1, 1, '', NULL, now(), now(), NULL);
INSERT INTO `sys_menu` VALUES (2512, 251, '0,1,251', '字典项新增', 'B', NULL, '', NULL, 'sys:dict-item:create', NULL, NULL, 1, 2, '', NULL, now(), now(), NULL);
INSERT INTO `sys_menu` VALUES (2513, 251, '0,1,251', '字典项编辑', 'B', NULL, '', NULL, 'sys:dict-item:update', NULL, NULL, 1, 3, '', NULL, now(), now(), NULL);
INSERT INTO `sys_menu` VALUES (2514, 251, '0,1,251', '字典项删除', 'B', NULL, '', NULL, 'sys:dict-item:delete', NULL, NULL, 1, 4, '', NULL, now(), now(), NULL);
INSERT INTO `sys_menu` VALUES (260, 1, '0,1', '系统日志', 'M', 'Log', 'log', 'system/log/index', NULL, 0, 1, 1, 7, 'document', NULL, now(), now(), NULL);
INSERT INTO `sys_menu` VALUES (2601, 260, '0,1,260', '日志查询', 'B', NULL, '', NULL, 'sys:log:list', NULL, NULL, 1, 1, '', NULL, now(), now(), NULL);
INSERT INTO `sys_menu` VALUES (270, 1, '0,1', '系统配置', 'M', 'Config', 'config', 'system/config/index', NULL, 0, 1, 1, 8, 'setting', NULL, now(), now(), NULL);
INSERT INTO `sys_menu` VALUES (2701, 270, '0,1,270', '系统配置查询', 'B', NULL, '', NULL, 'sys:config:list', 0, 1, 1, 1, '', NULL, now(), now(), NULL);
INSERT INTO `sys_menu` VALUES (2702, 270, '0,1,270', '系统配置新增', 'B', NULL, '', NULL, 'sys:config:create', 0, 1, 1, 2, '', NULL, now(), now(), NULL);
INSERT INTO `sys_menu` VALUES (2703, 270, '0,1,270', '系统配置修改', 'B', NULL, '', NULL, 'sys:config:update', 0, 1, 1, 3, '', NULL, now(), now(), NULL);
INSERT INTO `sys_menu` VALUES (2704, 270, '0,1,270', '系统配置删除', 'B', NULL, '', NULL, 'sys:config:delete', 0, 1, 1, 4, '', NULL, now(), now(), NULL);
INSERT INTO `sys_menu` VALUES (2705, 270, '0,1,270', '系统配置刷新', 'B', NULL, '', NULL, 'sys:config:refresh', 0, 1, 1, 5, '', NULL, now(), now(), NULL);
INSERT INTO `sys_menu` VALUES (280, 1, '0,1', '通知公告', 'M', 'Notice', 'notice', 'system/notice/index', NULL, NULL, NULL, 1, 9, '', NULL, now(), now(), NULL);
INSERT INTO `sys_menu` VALUES (2801, 280, '0,1,280', '通知查询', 'B', NULL, '', NULL, 'sys:notice:list', NULL, NULL, 1, 1, '', NULL, now(), now(), NULL);
INSERT INTO `sys_menu` VALUES (2802, 280, '0,1,280', '通知新增', 'B', NULL, '', NULL, 'sys:notice:create', NULL, NULL, 1, 2, '', NULL, now(), now(), NULL);
INSERT INTO `sys_menu` VALUES (2803, 280, '0,1,280', '通知编辑', 'B', NULL, '', NULL, 'sys:notice:update', NULL, NULL, 1, 3, '', NULL, now(), now(), NULL);
INSERT INTO `sys_menu` VALUES (2804, 280, '0,1,280', '通知删除', 'B', NULL, '', NULL, 'sys:notice:delete', NULL, NULL, 1, 4, '', NULL, now(), now(), NULL);
INSERT INTO `sys_menu` VALUES (2805, 280, '0,1,280', '通知发布', 'B', NULL, '', NULL, 'sys:notice:publish', 0, 1, 1, 5, '', NULL, now(), now(), NULL);
INSERT INTO `sys_menu` VALUES (2806, 280, '0,1,280', '通知撤回', 'B', NULL, '', NULL, 'sys:notice:revoke', 0, 1, 1, 6, '', NULL, now(), now(), NULL);
-- 代码生成
INSERT INTO `sys_menu` VALUES (310, 2, '0,2', '代码生成', 'M', 'Codegen', 'codegen', 'codegen/index', NULL, NULL, 1, 1, 1, 'code', NULL, now(), now(), NULL);
-- 平台文档(外链通过 route_path 识别)
INSERT INTO `sys_menu` VALUES (501, 4, '0,4', '平台文档(外链)', 'M', NULL, 'https://juejin.cn/post/7228990409909108793', '', NULL, NULL, NULL, 1, 1, 'document', '', now(), now(), NULL);
INSERT INTO `sys_menu` VALUES (502, 4, '0,4', '后端文档', 'M', NULL, 'https://youlai.blog.csdn.net/article/details/145178880', '', NULL, NULL, NULL, 1, 2, 'document', '', now(), now(), NULL);
INSERT INTO `sys_menu` VALUES (503, 4, '0,4', '移动端文档', 'M', NULL, 'https://youlai.blog.csdn.net/article/details/143222890', '', NULL, NULL, NULL, 1, 3, 'document', '', now(), now(), NULL);
INSERT INTO `sys_menu` VALUES (504, 4, '0,4', '内部文档', 'M', NULL, 'internal-doc', 'demo/internal-doc', NULL, NULL, NULL, 1, 4, 'document', '', now(), now(), NULL);
-- 接口文档
INSERT INTO `sys_menu` VALUES (601, 5, '0,5', 'Apifox', 'M', 'Apifox', 'apifox', 'demo/api/apifox', NULL, NULL, 1, 1, 1, 'api', '', now(), now(), NULL);
-- 组件封装
INSERT INTO `sys_menu` VALUES (701, 6, '0,6', '富文本编辑器', 'M', 'WangEditor', 'wang-editor', 'demo/wang-editor', NULL, NULL, 1, 1, 2, '', '', now(), now(), NULL);
INSERT INTO `sys_menu` VALUES (702, 6, '0,6', '图片上传', 'M', 'Upload', 'upload', 'demo/upload', NULL, NULL, 1, 1, 3, '', '', now(), now(), NULL);
INSERT INTO `sys_menu` VALUES (703, 6, '0,6', '图标选择器', 'M', 'IconSelect', 'icon-select', 'demo/icon-select', NULL, NULL, 1, 1, 4, '', '', now(), now(), NULL);
INSERT INTO `sys_menu` VALUES (704, 6, '0,6', '字典组件', 'M', 'DictDemo', 'dict-demo', 'demo/dictionary', NULL, NULL, 1, 1, 4, '', '', now(), now(), NULL);
INSERT INTO `sys_menu` VALUES (705, 6, '0,6', '增删改查', 'M', 'Curd', 'curd', 'demo/curd/index', NULL, NULL, 1, 1, 0, '', '', now(), now(), NULL);
INSERT INTO `sys_menu` VALUES (706, 6, '0,6', '列表选择器', 'M', 'TableSelect', 'table-select', 'demo/table-select/index', NULL, NULL, 1, 1, 1, '', '', now(), now(), NULL);
INSERT INTO `sys_menu` VALUES (707, 6, '0,6', '拖拽组件', 'M', 'Drag', 'drag', 'demo/drag', NULL, NULL, NULL, 1, 5, '', '', now(), now(), NULL);
INSERT INTO `sys_menu` VALUES (708, 6, '0,6', '滚动文本', 'M', 'TextScroll', 'text-scroll', 'demo/text-scroll', NULL, NULL, NULL, 1, 6, '', '', now(), now(), NULL);
INSERT INTO `sys_menu` VALUES (709, 6, '0,6', '自适应表格操作列', 'M', 'AutoOperationColumn', 'operation-column', 'demo/auto-operation-column', NULL, NULL, 1, 1, 1, '', '', now(), now(), NULL);
-- 功能演示
INSERT INTO `sys_menu` VALUES (801, 7, '0,7', 'Icons', 'M', 'IconDemo', 'icon-demo', 'demo/icons', NULL, NULL, 1, 1, 2, 'el-icon-Notification', '', now(), now(), NULL);
INSERT INTO `sys_menu` VALUES (802, 7, '0,7', '字典实时同步', 'M', 'DictSync', 'dict-sync', 'demo/dict-sync', NULL, NULL, NULL, 1, 3, '', '', now(), now(), NULL);
INSERT INTO `sys_menu` VALUES (803, 7, '0,7', 'VxeTable', 'M', 'VxeTable', 'vxe-table', 'demo/vxe-table/index', NULL, NULL, 1, 1, 4, 'el-icon-MagicStick', '', now(), now(), NULL);
INSERT INTO `sys_menu` VALUES (804, 7, '0,7', 'CURD单文件', 'M', 'CurdSingle', 'curd-single', 'demo/curd-single', NULL, NULL, 1, 1, 5, 'el-icon-Reading', '', now(), now(), NULL);
-- 多级菜单示例
INSERT INTO `sys_menu` VALUES (910, 8, '0,8', '菜单一级', 'C', NULL, 'multi-level1', 'Layout', NULL, 1, NULL, 1, 1, '', '', now(), now(), NULL);
INSERT INTO `sys_menu` VALUES (911, 910, '0,8,910', '菜单二级', 'C', NULL, 'multi-level2', 'Layout', NULL, 0, NULL, 1, 1, '', NULL, now(), now(), NULL);
INSERT INTO `sys_menu` VALUES (912, 911, '0,8,910,911', '菜单三级-1', 'M', NULL, 'multi-level3-1', 'demo/multi-level/children/children/level3-1', NULL, 0, 1, 1, 1, '', '', now(), now(), NULL);
INSERT INTO `sys_menu` VALUES (913, 911, '0,8,910,911', '菜单三级-2', 'M', NULL, 'multi-level3-2', 'demo/multi-level/children/children/level3-2', NULL, 0, 1, 1, 2, '', '', now(), now(), NULL);
-- 路由参数
INSERT INTO `sys_menu` VALUES (1001, 9, '0,9', '参数(type=1)', 'M', 'RouteParamType1', 'route-param-type1', 'demo/route-param', NULL, 0, 1, 1, 1, 'el-icon-Star', NULL, now(), now(), '{\"type\": \"1\"}');
INSERT INTO `sys_menu` VALUES (1002, 9, '0,9', '参数(type=2)', 'M', 'RouteParamType2', 'route-param-type2', 'demo/route-param', NULL, 0, 1, 1, 2, 'el-icon-StarFilled', NULL, now(), now(), '{\"type\": \"2\"}');
-- ----------------------------
-- Table structure for sys_role
-- ----------------------------
DROP TABLE IF EXISTS `sys_role`;
CREATE TABLE `sys_role` (
`id` bigint NOT NULL AUTO_INCREMENT,
`name` varchar(64) NOT NULL COMMENT '角色名称',
`code` varchar(32) NOT NULL COMMENT '角色编码',
`sort` int NULL COMMENT '显示顺序',
`status` tinyint(1) DEFAULT 1 COMMENT '角色状态(1-正常 0-停用)',
`data_scope` tinyint NULL COMMENT '数据权限(1-所有数据 2-部门及子部门数据 3-本部门数据 4-本人数据 5-自定义部门数据)',
`create_by` bigint NULL COMMENT '创建人 ID',
`create_time` datetime NULL COMMENT '创建时间',
`update_by` bigint NULL COMMENT '更新人ID',
`update_time` datetime NULL COMMENT '更新时间',
`is_deleted` tinyint(1) DEFAULT 0 COMMENT '逻辑删除标识(0-未删除 1-已删除)',
PRIMARY KEY (`id`) USING BTREE,
UNIQUE INDEX `uk_name`(`name` ASC) USING BTREE COMMENT '角色名称唯一索引',
UNIQUE INDEX `uk_code`(`code` ASC) USING BTREE COMMENT '角色编码唯一索引'
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COMMENT = '系统角色表';
-- ----------------------------
-- Records of sys_role
-- ----------------------------
INSERT INTO `sys_role` VALUES (1, '超级管理员', 'ROOT', 1, 1, 1, NULL, now(), NULL, now(), 0);
INSERT INTO `sys_role` VALUES (2, '系统管理员', 'ADMIN', 2, 1, 1, NULL, now(), NULL, NULL, 0);
INSERT INTO `sys_role` VALUES (3, '访问游客', 'GUEST', 3, 1, 3, NULL, now(), NULL, now(), 0);
INSERT INTO `sys_role` VALUES (4, '部门主管', 'DEPT_MANAGER', 4, 1, 2, NULL, now(), NULL, now(), 0);
INSERT INTO `sys_role` VALUES (5, '部门成员', 'DEPT_MEMBER', 5, 1, 3, NULL, now(), NULL, now(), 0);
INSERT INTO `sys_role` VALUES (6, '普通员工', 'EMPLOYEE', 6, 1, 4, NULL, now(), NULL, now(), 0);
INSERT INTO `sys_role` VALUES (7, '自定义权限用户', 'CUSTOM_USER', 7, 1, 5, NULL, now(), NULL, now(), 0);
-- ----------------------------
-- Table structure for sys_role_menu
-- ----------------------------
DROP TABLE IF EXISTS `sys_role_menu`;
CREATE TABLE `sys_role_menu` (
`role_id` bigint NOT NULL COMMENT '角色ID',
`menu_id` bigint NOT NULL COMMENT '菜单ID',
UNIQUE INDEX `uk_roleid_menuid`(`role_id` ASC, `menu_id` ASC) USING BTREE COMMENT '角色菜单唯一索引'
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COMMENT = '角色菜单关联表';
-- ----------------------------
-- Table structure for sys_role_dept
-- ----------------------------
DROP TABLE IF EXISTS `sys_role_dept`;
CREATE TABLE `sys_role_dept` (
`role_id` bigint NOT NULL COMMENT '角色ID',
`dept_id` bigint NOT NULL COMMENT '部门ID',
UNIQUE INDEX `uk_roleid_deptid`(`role_id` ASC, `dept_id` ASC) USING BTREE COMMENT '角色部门唯一索引'
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COMMENT = '角色部门关联表';
-- ----------------------------
-- Records of sys_role_dept
-- ----------------------------
INSERT IGNORE INTO `sys_role_dept` VALUES (7, 1);
INSERT IGNORE INTO `sys_role_dept` VALUES (7, 2);
-- ============================================
-- 系统管理员角色菜单权限(role_id=2)
-- 顶级目录
INSERT INTO `sys_role_menu` VALUES (2, 1), (2, 2), (2, 4), (2, 5), (2, 6), (2, 7), (2, 8), (2, 9);
-- 系统管理
INSERT INTO `sys_role_menu` VALUES (2, 210), (2, 2101), (2, 2102), (2, 2103), (2, 2104), (2, 2105), (2, 2106), (2, 2107);
INSERT INTO `sys_role_menu` VALUES (2, 220), (2, 2201), (2, 2202), (2, 2203), (2, 2204), (2, 2205);
INSERT INTO `sys_role_menu` VALUES (2, 230), (2, 2301), (2, 2302), (2, 2303), (2, 2304);
INSERT INTO `sys_role_menu` VALUES (2, 240), (2, 2401), (2, 2402), (2, 2403), (2, 2404);
INSERT INTO `sys_role_menu` VALUES (2, 250), (2, 2501), (2, 2502), (2, 2503), (2, 2504);
INSERT INTO `sys_role_menu` VALUES (2, 251), (2, 2511), (2, 2512), (2, 2513), (2, 2514);
INSERT INTO `sys_role_menu` VALUES (2, 260), (2, 2601);
INSERT INTO `sys_role_menu` VALUES (2, 270), (2, 2701), (2, 2702), (2, 2703), (2, 2704), (2, 2705);
INSERT INTO `sys_role_menu` VALUES (2, 280), (2, 2801), (2, 2802), (2, 2803), (2, 2804), (2, 2805), (2, 2806);
INSERT IGNORE INTO `sys_role_menu` VALUES (4, 1);
INSERT IGNORE INTO `sys_role_menu` VALUES (4, 210), (4, 2101), (4, 2102), (4, 2103), (4, 2104), (4, 2105), (4, 2106), (4, 2107);
INSERT IGNORE INTO `sys_role_menu` VALUES (4, 220), (4, 2201), (4, 2202), (4, 2203), (4, 2204), (4, 2205);
INSERT IGNORE INTO `sys_role_menu` VALUES (5, 1);
INSERT IGNORE INTO `sys_role_menu` VALUES (5, 210), (5, 2101), (5, 2102), (5, 2103), (5, 2104), (5, 2105), (5, 2106), (5, 2107);
INSERT IGNORE INTO `sys_role_menu` VALUES (5, 220), (5, 2201), (5, 2202), (5, 2203), (5, 2204), (5, 2205);
INSERT IGNORE INTO `sys_role_menu` VALUES (6, 1);
INSERT IGNORE INTO `sys_role_menu` VALUES (6, 210), (6, 2101), (6, 2102), (6, 2103), (6, 2104), (6, 2105), (6, 2106), (6, 2107);
INSERT IGNORE INTO `sys_role_menu` VALUES (6, 220), (6, 2201), (6, 2202), (6, 2203), (6, 2204), (6, 2205);
INSERT IGNORE INTO `sys_role_menu` VALUES (7, 1);
INSERT IGNORE INTO `sys_role_menu` VALUES (7, 210), (7, 2101), (7, 2102), (7, 2103), (7, 2104), (7, 2105), (7, 2106), (7, 2107);
INSERT IGNORE INTO `sys_role_menu` VALUES (7, 220), (7, 2201), (7, 2202), (7, 2203), (7, 2204), (7, 2205);
-- 代码生成
INSERT INTO `sys_role_menu` VALUES (2, 310);
-- 平台文档
INSERT INTO `sys_role_menu` VALUES (2, 501), (2, 502), (2, 503), (2, 504);
-- 接口文档
INSERT INTO `sys_role_menu` VALUES (2, 601);
-- 组件封装
INSERT INTO `sys_role_menu` VALUES (2, 701), (2, 702), (2, 703), (2, 704), (2, 705), (2, 706), (2, 707), (2, 708), (2, 709);
-- 功能演示 / 多级菜单
INSERT INTO `sys_role_menu` VALUES (2, 801), (2, 802), (2, 803), (2, 804), (2, 910), (2, 911), (2, 912), (2, 913);
-- 路由参数
INSERT INTO `sys_role_menu` VALUES (2, 1001), (2, 1002);
-- ----------------------------
-- Table structure for sys_user
-- ----------------------------
DROP TABLE IF EXISTS `sys_user`;
CREATE TABLE `sys_user` (
`id` bigint NOT NULL AUTO_INCREMENT,
`username` varchar(64) COMMENT '用户名',
`nickname` varchar(64) COMMENT '昵称',
`gender` tinyint(1) DEFAULT 1 COMMENT '性别((1-男 2-女 0-保密)',
`password` varchar(100) COMMENT '密码',
`dept_id` int COMMENT '部门ID',
`avatar` varchar(255) COMMENT '用户头像',
`mobile` varchar(20) COMMENT '联系方式',
`status` tinyint(1) DEFAULT 1 COMMENT '状态(1-正常 0-禁用)',
`email` varchar(128) COMMENT '用户邮箱',
`create_time` datetime COMMENT '创建时间',
`create_by` bigint COMMENT '创建人ID',
`update_time` datetime COMMENT '更新时间',
`update_by` bigint COMMENT '修改人ID',
`is_deleted` tinyint(1) DEFAULT 0 COMMENT '逻辑删除标识(0-未删除 1-已删除)',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COMMENT = '系统用户表';
-- ----------------------------
-- Records of sys_user
-- ----------------------------
INSERT INTO `sys_user` VALUES (1, 'root', '有来技术', 0, '$2a$10$xVWsNOhHrCxh5UbpCE7/HuJ.PAOKcYAqRxD2CO2nVnJS.IAXkr5aq', NULL, 'https://foruda.gitee.com/images/1723603502796844527/03cdca2a_716974.gif', '18812345677', 1, 'youlaitech@163.com', now(), NULL, now(), NULL, 0);
INSERT INTO `sys_user` VALUES (2, 'admin', '系统管理员', 1, '$2a$10$xVWsNOhHrCxh5UbpCE7/HuJ.PAOKcYAqRxD2CO2nVnJS.IAXkr5aq', 1, 'https://foruda.gitee.com/images/1723603502796844527/03cdca2a_716974.gif', '18888888888', 1, 'youlaitech@163.com', now(), NULL, now(), NULL, 0);
INSERT INTO `sys_user` VALUES (3, 'test', '测试小用户', 1, '$2a$10$xVWsNOhHrCxh5UbpCE7/HuJ.PAOKcYAqRxD2CO2nVnJS.IAXkr5aq', 3, 'https://foruda.gitee.com/images/1723603502796844527/03cdca2a_716974.gif', '18812345679', 1, 'youlaitech@163.com', now(), NULL, now(), NULL, 0);
INSERT INTO `sys_user` VALUES (4, 'dept_manager', '部门主管', 1, '$2a$10$xVWsNOhHrCxh5UbpCE7/HuJ.PAOKcYAqRxD2CO2nVnJS.IAXkr5aq', 1, 'https://foruda.gitee.com/images/1723603502796844527/03cdca2a_716974.gif', '18812345680', 1, 'manager@youlaitech.com', now(), NULL, now(), NULL, 0);
INSERT INTO `sys_user` VALUES (5, 'dept_member', '部门成员', 1, '$2a$10$xVWsNOhHrCxh5UbpCE7/HuJ.PAOKcYAqRxD2CO2nVnJS.IAXkr5aq', 1, 'https://foruda.gitee.com/images/1723603502796844527/03cdca2a_716974.gif', '18812345681', 1, 'member@youlaitech.com', now(), NULL, now(), NULL, 0);
INSERT INTO `sys_user` VALUES (6, 'employee', '普通员工', 1, '$2a$10$xVWsNOhHrCxh5UbpCE7/HuJ.PAOKcYAqRxD2CO2nVnJS.IAXkr5aq', 2, 'https://foruda.gitee.com/images/1723603502796844527/03cdca2a_716974.gif', '18812345682', 1, 'employee@youlaitech.com', now(), NULL, now(), NULL, 0);
INSERT INTO `sys_user` VALUES (7, 'custom_user', '自定义权限用户', 1, '$2a$10$xVWsNOhHrCxh5UbpCE7/HuJ.PAOKcYAqRxD2CO2nVnJS.IAXkr5aq', 3, 'https://foruda.gitee.com/images/1723603502796844527/03cdca2a_716974.gif', '18812345683', 1, 'custom@youlaitech.com', now(), NULL, now(), NULL, 0);
-- ----------------------------
-- Table structure for sys_user_role
-- ----------------------------
DROP TABLE IF EXISTS `sys_user_role`;
CREATE TABLE `sys_user_role` (
`user_id` bigint NOT NULL COMMENT '用户ID',
`role_id` bigint NOT NULL COMMENT '角色ID',
PRIMARY KEY (`user_id`, `role_id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COMMENT = '用户角色关联表';
-- ----------------------------
-- Records of sys_user_role
-- ----------------------------
INSERT IGNORE INTO `sys_user_role` VALUES (1, 1);
INSERT IGNORE INTO `sys_user_role` VALUES (2, 2);
INSERT IGNORE INTO `sys_user_role` VALUES (3, 3);
INSERT IGNORE INTO `sys_user_role` VALUES (4, 4);
INSERT IGNORE INTO `sys_user_role` VALUES (5, 5);
INSERT IGNORE INTO `sys_user_role` VALUES (6, 6);
INSERT IGNORE INTO `sys_user_role` VALUES (7, 7);
-- ----------------------------
-- Table structure for sys_log
-- ----------------------------
DROP TABLE IF EXISTS `sys_log`;
CREATE TABLE `sys_log` (
`id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键',
`module` TINYINT NOT NULL COMMENT '模块,数字枚举,参考 LogModule 枚举',
`action_type` TINYINT NOT NULL COMMENT '操作类型,数字枚举,参考 ActionType 枚举',
`title` VARCHAR(100) NOT NULL COMMENT '前端显示标题',
`content` TEXT COMMENT '自定义日志内容',
`operator_id` BIGINT COMMENT '操作人ID',
`operator_name` VARCHAR(50) COMMENT '操作人名称',
`request_uri` VARCHAR(255) COMMENT '请求路径',
`request_method` VARCHAR(10) COMMENT '请求方法',
`ip` VARCHAR(45) COMMENT 'IP地址',
`province` VARCHAR(100) COMMENT '省份',
`city` VARCHAR(100) COMMENT '城市',
`device` VARCHAR(100) COMMENT '设备',
`os` VARCHAR(100) COMMENT '操作系统',
`browser` VARCHAR(100) COMMENT '浏览器',
`status` TINYINT DEFAULT 1 COMMENT '0失败 1成功',
`error_msg` VARCHAR(255) COMMENT '错误信息',
`execution_time` INT COMMENT '执行时间(ms)',
`create_time` DATETIME COMMENT '操作时间',
PRIMARY KEY (`id`) USING BTREE,
KEY `idx_module_action_time` (`module`, `action_type`, `create_time`),
KEY `idx_operator_time` (`operator_id`, `create_time`),
KEY `idx_time` (`create_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='系统操作日志表';
-- ----------------------------
-- Table structure for gen_table
-- ----------------------------
DROP TABLE IF EXISTS `gen_table`;
CREATE TABLE `gen_table` (
`id` bigint NOT NULL AUTO_INCREMENT,
`table_name` varchar(100) NOT NULL COMMENT '表名',
`module_name` varchar(100) COMMENT '模块名',
`package_name` varchar(255) NOT NULL COMMENT '包名',
`business_name` varchar(100) NOT NULL COMMENT '业务名',
`entity_name` varchar(100) NOT NULL COMMENT '实体类名',
`author` varchar(50) NOT NULL COMMENT '作者',
`parent_menu_id` bigint COMMENT '上级菜单ID,对应sys_menu的id ',
`remove_table_prefix` varchar(20) COMMENT '要移除的表前缀,如: sys_',
`page_type` varchar(20) COMMENT '页面类型(classic|curd)',
`create_time` datetime COMMENT '创建时间',
`update_time` datetime COMMENT '更新时间',
`is_deleted` tinyint(4) DEFAULT 0 COMMENT '是否删除',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_tablename` (`table_name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='代码生成配置表';
-- ----------------------------
-- Table structure for gen_table_column
-- ----------------------------
DROP TABLE IF EXISTS `gen_table_column`;
CREATE TABLE `gen_table_column` (
`id` bigint NOT NULL AUTO_INCREMENT,
`table_id` bigint NOT NULL COMMENT '关联的表配置ID',
`column_name` varchar(100) ,
`column_type` varchar(50) ,
`column_length` int ,
`field_name` varchar(100) NOT NULL COMMENT '字段名称',
`field_type` varchar(100) COMMENT '字段类型',
`field_sort` int COMMENT '字段排序',
`field_comment` varchar(255) COMMENT '字段描述',
`max_length` int ,
`is_required` tinyint(1) COMMENT '是否必填',
`is_show_in_list` tinyint(1) DEFAULT '0' COMMENT '是否在列表显示',
`is_show_in_form` tinyint(1) DEFAULT '0' COMMENT '是否在表单显示',
`is_show_in_query` tinyint(1) DEFAULT '0' COMMENT '是否在查询条件显示',
`query_type` tinyint COMMENT '查询方式',
`form_type` tinyint COMMENT '表单类型',
`dict_type` varchar(50) COMMENT '字典类型',
`create_time` datetime COMMENT '创建时间',
`update_time` datetime COMMENT '更新时间',
PRIMARY KEY (`id`),
KEY `idx_table_id` (`table_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='代码生成字段配置表';
-- ----------------------------
-- 系统配置表
-- ----------------------------
DROP TABLE IF EXISTS `sys_config`;
CREATE TABLE `sys_config` (
`id` bigint NOT NULL AUTO_INCREMENT,
`config_name` varchar(50) NOT NULL COMMENT '配置名称',
`config_key` varchar(50) NOT NULL COMMENT '配置key',
`config_value` text NOT NULL COMMENT '配置值',
`remark` varchar(255) COMMENT '备注',
`create_time` datetime COMMENT '创建时间',
`create_by` bigint COMMENT '创建人ID',
`update_time` datetime COMMENT '更新时间',
`update_by` bigint COMMENT '更新人ID',
`is_deleted` tinyint(4) DEFAULT '0' NOT NULL COMMENT '逻辑删除标识(0-未删除 1-已删除)',
PRIMARY KEY (`id`)
) ENGINE=InnoDB COMMENT='系统配置表';
INSERT INTO `sys_config` VALUES (1, '系统限流QPS', 'IP_QPS_THRESHOLD_LIMIT', '10', '单个IP请求的最大每秒查询数(QPS)阈值Key', now(), 1, NULL, NULL, 0);
-- ----------------------------
-- 通知公告表
-- ----------------------------
DROP TABLE IF EXISTS `sys_notice`;
CREATE TABLE `sys_notice` (
`id` bigint NOT NULL AUTO_INCREMENT,
`title` varchar(50) COMMENT '通知标题',
`content` text COMMENT '通知内容',
`type` tinyint NOT NULL COMMENT '通知类型(关联字典编码:notice_type',
`level` varchar(5) NOT NULL COMMENT '通知等级(字典codenotice_level',
`target_type` tinyint NOT NULL COMMENT '目标类型(1: 全体, 2: 指定)',
`target_user_ids` varchar(255) COMMENT '目标人ID集合(多个使用英文逗号,分割)',
`publisher_id` bigint COMMENT '发布人ID',
`publish_status` tinyint DEFAULT '0' COMMENT '发布状态(0: 未发布, 1: 已发布, -1: 已撤回)',
`publish_time` datetime COMMENT '发布时间',
`revoke_time` datetime COMMENT '撤回时间',
`create_by` bigint NOT NULL COMMENT '创建人ID',
`create_time` datetime NOT NULL COMMENT '创建时间',
`update_by` bigint COMMENT '更新人ID',
`update_time` datetime COMMENT '更新时间',
`is_deleted` tinyint(1) DEFAULT '0' COMMENT '是否删除(0: 未删除, 1: 已删除)',
PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='系统通知公告表';
INSERT INTO `sys_notice` VALUES (1, 'v3.0.0 版本发布 - 多租户功能上线', '<p>🎉 新版本发布,主要更新内容:</p><p>1. 新增多租户功能,支持租户隔离和数据管理</p><p>2. 优化系统性能,提升响应速度</p><p>3. 完善权限管理,增强安全性</p><p>4. 修复已知问题,提升系统稳定性</p>', 1, 'H', 1, NULL, 1, 1, '2024-12-15 10:00:00', NULL, 1, '2024-12-15 10:00:00', 1, '2024-12-15 10:00:00', 0);
INSERT INTO `sys_notice` VALUES (2, '系统维护通知 - 2024年12月20日', '<p>⏰ 系统维护通知</p><p>系统将于 <strong>2024年12月20日(本周五)凌晨 2:00-4:00</strong> 进行例行维护升级。</p><p>维护期间系统将暂停服务,请提前做好数据备份工作。</p><p>给您带来的不便,敬请谅解!</p>', 2, 'H', 1, NULL, 1, 1, '2024-12-18 14:30:00', NULL, 1, '2024-12-18 14:30:00', 1, '2024-12-18 14:30:00', 0);
INSERT INTO `sys_notice` VALUES (3, '安全提醒 - 防范钓鱼邮件', '<p>⚠️ 安全提醒</p><p>近期发现有不法分子通过钓鱼邮件进行网络攻击,请大家提高警惕:</p><p>1. 不要点击来源不明的邮件链接</p><p>2. 不要下载可疑附件</p><p>3. 遇到可疑邮件请及时联系IT部门</p><p>4. 定期修改密码,使用强密码策略</p>', 3, 'H', 1, NULL, 1, 1, '2024-12-10 09:00:00', NULL, 1, '2024-12-10 09:00:00', 1, '2024-12-10 09:00:00', 0);
INSERT INTO `sys_notice` VALUES (4, '元旦假期安排通知', '<p>📅 元旦假期安排</p><p>根据国家法定节假日安排,公司元旦假期时间为:</p><p><strong>2024年12月30日(周一)至 2025年1月1日(周三)</strong>,共3天。</p><p>2024年12月29日(周日)正常上班。</p><p>祝大家元旦快乐,假期愉快!</p>', 4, 'M', 1, NULL, 1, 1, '2024-12-25 16:00:00', NULL, 1, '2024-12-25 16:00:00', 1, '2024-12-25 16:00:00', 0);
INSERT INTO `sys_notice` VALUES (5, '新产品发布会邀请', '<p>🎊 新产品发布会邀请</p><p>公司将于 <strong>2025年1月15日下午14:00</strong> 在总部会议室举办新产品发布会。</p><p>届时将展示最新研发的产品和技术成果,欢迎全体员工参加。</p><p>请各部门提前安排好工作,准时参加。</p>', 5, 'M', 1, NULL, 1, 1, '2024-12-28 11:00:00', NULL, 1, '2024-12-28 11:00:00', 1, '2024-12-28 11:00:00', 0);
INSERT INTO `sys_notice` VALUES (6, 'v2.16.1 版本更新', '<p>✨ 版本更新</p><p>v2.16.1 版本已发布,主要修复内容:</p><p>1. 修复 WebSocket 重复连接导致的后台线程阻塞问题</p><p>2. 优化通知公告功能,提升用户体验</p><p>3. 修复部分已知bug</p><p>建议尽快更新到最新版本。</p>', 1, 'M', 1, NULL, 1, 1, '2024-12-05 15:30:00', NULL, 1, '2024-12-05 15:30:00', 1, '2024-12-05 15:30:00', 0);
INSERT INTO `sys_notice` VALUES (7, '年终总结会议通知', '<p>📋 年终总结会议通知</p><p>各部门年终总结会议将于 <strong>2024年12月30日上午9:00</strong> 召开。</p><p>请各部门负责人提前准备好年度工作总结和下年度工作计划。</p><p>会议地点:总部大会议室</p>', 5, 'M', 2, '1,2', 1, 1, '2024-12-22 10:00:00', NULL, 1, '2024-12-22 10:00:00', 1, '2024-12-22 10:00:00', 0);
INSERT INTO `sys_notice` VALUES (8, '系统功能优化完成', '<p>✅ 系统功能优化</p><p>已完成以下功能优化:</p><p>1. 优化用户管理界面,提升操作体验</p><p>2. 增强数据导出功能,支持更多格式</p><p>3. 优化搜索功能,提升查询效率</p><p>4. 修复部分界面显示问题</p>', 1, 'L', 1, NULL, 1, 1, '2024-12-12 14:20:00', NULL, 1, '2024-12-12 14:20:00', 1, '2024-12-12 14:20:00', 0);
INSERT INTO `sys_notice` VALUES (9, '员工培训计划', '<p>📚 员工培训计划</p><p>为提升员工专业技能,公司将于 <strong>2025年1月8日-10日</strong> 组织技术培训。</p><p>培训内容:</p><p>1. 新技术框架应用</p><p>2. 代码规范与最佳实践</p><p>3. 系统架构设计</p><p>请各部门合理安排工作,确保培训顺利进行。</p>', 5, 'M', 1, NULL, 1, 1, '2024-12-20 09:30:00', NULL, 1, '2024-12-20 09:30:00', 1, '2024-12-20 09:30:00', 0);
INSERT INTO `sys_notice` VALUES (10, '数据备份提醒', '<p>💾 数据备份提醒</p><p>请各部门注意定期备份重要数据,建议每周至少备份一次。</p><p>备份方式:</p><p>1. 使用系统自带备份功能</p><p>2. 手动导出重要数据</p><p>3. 联系IT部门协助备份</p><p>数据安全,人人有责!</p>', 3, 'L', 1, NULL, 1, 1, '2024-12-08 08:00:00', NULL, 1, '2024-12-08 08:00:00', 1, '2024-12-08 08:00:00', 0);
-- ----------------------------
-- 用户通知公告表
-- ----------------------------
DROP TABLE IF EXISTS `sys_user_notice`;
CREATE TABLE `sys_user_notice` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT 'id',
`notice_id` bigint NOT NULL COMMENT '公共通知id',
`user_id` bigint NOT NULL COMMENT '用户id',
`is_read` tinyint DEFAULT '0' COMMENT '读取状态(0: 未读, 1: 已读)',
`read_time` datetime COMMENT '阅读时间',
`create_time` datetime NOT NULL COMMENT '创建时间',
`update_time` datetime COMMENT '更新时间',
`is_deleted` tinyint DEFAULT '0' COMMENT '逻辑删除(0: 未删除, 1: 已删除)',
PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户通知公告关联表';
INSERT INTO `sys_user_notice` VALUES (1, 1, 2, 1, NULL, now(), now(), 0);
INSERT INTO `sys_user_notice` VALUES (2, 2, 2, 1, NULL, now(), now(), 0);
INSERT INTO `sys_user_notice` VALUES (3, 3, 2, 1, NULL, now(), now(), 0);
INSERT INTO `sys_user_notice` VALUES (4, 4, 2, 1, NULL, now(), now(), 0);
INSERT INTO `sys_user_notice` VALUES (5, 5, 2, 1, NULL, now(), now(), 0);
INSERT INTO `sys_user_notice` VALUES (6, 6, 2, 1, NULL, now(), now(), 0);
INSERT INTO `sys_user_notice` VALUES (7, 7, 2, 1, NULL, now(), now(), 0);
INSERT INTO `sys_user_notice` VALUES (8, 8, 2, 1, NULL, now(), now(), 0);
INSERT INTO `sys_user_notice` VALUES (9, 9, 2, 1, NULL, now(), now(), 0);
INSERT INTO `sys_user_notice` VALUES (10, 10, 2, 1, NULL, now(), now(), 0);
-- ----------------------------
-- Table structure for sys_user_social
-- ----------------------------
DROP TABLE IF EXISTS `sys_user_social`;
CREATE TABLE `sys_user_social` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`user_id` bigint NOT NULL COMMENT '用户ID',
`platform` varchar(20) NOT NULL COMMENT '平台类型(WECHAT_MINI/WECHAT_MP/ALIPAY/QQ/APPLE)',
`openid` varchar(64) NOT NULL COMMENT '平台openid',
`unionid` varchar(64) DEFAULT NULL COMMENT '微信unionid',
`nickname` varchar(64) DEFAULT NULL COMMENT '第三方昵称',
`avatar` varchar(255) DEFAULT NULL COMMENT '第三方头像URL',
`session_key` varchar(128) DEFAULT NULL COMMENT '微信session_key',
`verified` tinyint(1) DEFAULT 1 COMMENT '是否已验证(1-已验证 0-未验证)',
`create_time` datetime DEFAULT NULL COMMENT '绑定时间',
`update_time` datetime DEFAULT NULL COMMENT '更新时间',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_platform_openid` (`platform`, `openid`),
KEY `idx_user_id` (`user_id`),
KEY `idx_unionid` (`unionid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户第三方账号绑定表';
+524
View File
@@ -0,0 +1,524 @@
# YouLai_Admin (MySQL 5.7 ~ MySQL 8.x)
# Copyright (c) 2021-present, youlai.tech
-- ----------------------------
-- 1. 创建数据库
-- ----------------------------
CREATE DATABASE IF NOT EXISTS youlai_admin_template CHARACTER SET utf8mb4 DEFAULT COLLATE utf8mb4_unicode_ci;
-- ----------------------------
-- 2. 创建表 && 数据初始化
-- ----------------------------
USE youlai_admin_template;
SET NAMES utf8mb4; #
SET FOREIGN_KEY_CHECKS = 0; #
-- ----------------------------
-- Table structure for sys_dept
-- ----------------------------
DROP TABLE IF EXISTS `sys_dept`;
CREATE TABLE `sys_dept` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键',
`name` varchar(100) NOT NULL COMMENT '部门名称',
`code` varchar(100) NOT NULL COMMENT '部门编号',
`parent_id` bigint DEFAULT 0 COMMENT '父节点id',
`tree_path` varchar(255) NOT NULL COMMENT '父节点id路径',
`sort` smallint DEFAULT 0 COMMENT '显示顺序',
`status` tinyint DEFAULT 1 COMMENT '状态(1-正常 0-禁用)',
`create_by` bigint NULL COMMENT '创建人ID',
`create_time` datetime NULL COMMENT '创建时间',
`update_by` bigint NULL COMMENT '修改人ID',
`update_time` datetime NULL COMMENT '更新时间',
`is_deleted` tinyint DEFAULT 0 COMMENT '逻辑删除标识(1-已删除 0-未删除)',
PRIMARY KEY (`id`) USING BTREE,
UNIQUE INDEX `uk_code`(`code` ASC) USING BTREE COMMENT '部门编号唯一索引'
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COMMENT = '部门管理表';
-- ----------------------------
-- Records of sys_dept
-- ----------------------------
INSERT INTO `sys_dept` VALUES (1, '有来技术', 'YOULAI', 0, '0', 1, 1, 1, NULL, 1, now(), 0);
INSERT INTO `sys_dept` VALUES (2, '研发部门', 'RD001', 1, '0,1', 1, 1, 2, NULL, 2, now(), 0);
INSERT INTO `sys_dept` VALUES (3, '测试部门', 'QA001', 1, '0,1', 1, 1, 2, NULL, 2, now(), 0);
-- ----------------------------
-- Table structure for sys_dict
-- ----------------------------
DROP TABLE IF EXISTS `sys_dict`;
CREATE TABLE `sys_dict` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键 ',
`dict_code` varchar(50) COMMENT '类型编码',
`name` varchar(50) COMMENT '类型名称',
`status` tinyint(1) DEFAULT '0' COMMENT '状态(0:正常;1:禁用)',
`remark` varchar(255) COMMENT '备注',
`create_time` datetime COMMENT '创建时间',
`create_by` bigint COMMENT '创建人ID',
`update_time` datetime COMMENT '更新时间',
`update_by` bigint COMMENT '修改人ID',
`is_deleted` tinyint DEFAULT '0' COMMENT '是否删除(1-删除,0-未删除)',
PRIMARY KEY (`id`) USING BTREE,
KEY `idx_dict_code` (`dict_code`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='数据字典类型表';
-- ----------------------------
-- Records of sys_dict
-- ----------------------------
INSERT INTO `sys_dict` VALUES (1, 'gender', '性别', 1, NULL, now() , 1,now(), 1,0);
INSERT INTO `sys_dict` VALUES (2, 'notice_type', '通知类型', 1, NULL, now(), 1,now(), 1,0);
INSERT INTO `sys_dict` VALUES (3, 'notice_level', '通知级别', 1, NULL, now(), 1,now(), 1,0);
-- ----------------------------
-- Table structure for sys_dict_item
-- ----------------------------
DROP TABLE IF EXISTS `sys_dict_item`;
CREATE TABLE `sys_dict_item` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键',
`dict_code` varchar(50) COMMENT '关联字典编码,与sys_dict表中的dict_code对应',
`value` varchar(50) COMMENT '字典项值',
`label` varchar(100) COMMENT '字典项标签',
`tag_type` varchar(50) COMMENT '标签类型,用于前端样式展示(如success、warning等)',
`status` tinyint DEFAULT '0' COMMENT '状态(1-正常,0-禁用)',
`sort` int DEFAULT '0' COMMENT '排序',
`remark` varchar(255) COMMENT '备注',
`create_time` datetime COMMENT '创建时间',
`create_by` bigint COMMENT '创建人ID',
`update_time` datetime COMMENT '更新时间',
`update_by` bigint COMMENT '修改人ID',
PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='数据字典项表';
-- ----------------------------
-- Records of sys_dict_item
-- ----------------------------
INSERT INTO `sys_dict_item` VALUES (1, 'gender', '1', '', 'primary', 1, 1, NULL, now(), 1,now(),1);
INSERT INTO `sys_dict_item` VALUES (2, 'gender', '2', '', 'danger', 1, 2, NULL, now(), 1,now(),1);
INSERT INTO `sys_dict_item` VALUES (3, 'gender', '0', '保密', 'info', 1, 3, NULL, now(), 1,now(),1);
INSERT INTO `sys_dict_item` VALUES (4, 'notice_type', '1', '系统升级', 'success', 1, 1, '', now(), 1,now(),1);
INSERT INTO `sys_dict_item` VALUES (5, 'notice_type', '2', '系统维护', 'primary', 1, 2, '', now(), 1,now(),1);
INSERT INTO `sys_dict_item` VALUES (6, 'notice_type', '3', '安全警告', 'danger', 1, 3, '', now(), 1,now(),1);
INSERT INTO `sys_dict_item` VALUES (7, 'notice_type', '4', '假期通知', 'success', 1, 4, '', now(), 1,now(),1);
INSERT INTO `sys_dict_item` VALUES (8, 'notice_type', '5', '公司新闻', 'primary', 1, 5, '', now(), 1,now(),1);
INSERT INTO `sys_dict_item` VALUES (9, 'notice_type', '99', '其他', 'info', 1, 99, '', now(), 1,now(),1);
INSERT INTO `sys_dict_item` VALUES (10, 'notice_level', 'L', '', 'info', 1, 1, '', now(), 1,now(),1);
INSERT INTO `sys_dict_item` VALUES (11, 'notice_level', 'M', '', 'warning', 1, 2, '', now(), 1,now(),1);
INSERT INTO `sys_dict_item` VALUES (12, 'notice_level', 'H', '', 'danger', 1, 3, '', now(), 1,now(),1);
-- ----------------------------
-- Table structure for sys_menu
-- ----------------------------
DROP TABLE IF EXISTS `sys_menu`;
CREATE TABLE `sys_menu` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT 'ID',
`parent_id` bigint NOT NULL COMMENT '父菜单ID',
`tree_path` varchar(255) COMMENT '父节点ID路径',
`name` varchar(64) NOT NULL COMMENT '菜单名称',
`type` char(1) NOT NULL COMMENT '菜单类型(C-目录 M-菜单 B-按钮)',
`route_name` varchar(255) COMMENT '路由名称(Vue Router 中用于命名路由)',
`route_path` varchar(128) COMMENT '路由路径(Vue Router 中定义的 URL 路径)',
`component` varchar(128) COMMENT '组件路径(组件页面完整路径,相对于 src/views/,缺省后缀 .vue',
`perm` varchar(128) COMMENT '【按钮】权限标识',
`always_show` tinyint DEFAULT 0 COMMENT '【目录】只有一个子路由是否始终显示(1-是 0-否)',
`keep_alive` tinyint DEFAULT 0 COMMENT '【菜单】是否开启页面缓存(1-是 0-否)',
`visible` tinyint(1) DEFAULT 1 COMMENT '显示状态(1-显示 0-隐藏)',
`sort` int DEFAULT 0 COMMENT '排序',
`icon` varchar(64) COMMENT '菜单图标',
`redirect` varchar(128) COMMENT '跳转路径',
`create_time` datetime NULL COMMENT '创建时间',
`update_time` datetime NULL COMMENT '更新时间',
`params` varchar(255) NULL COMMENT '路由参数',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COMMENT = '系统菜单表';
-- ----------------------------
-- Records of sys_menu
-- ----------------------------
-- 顶级目录:系统管理/代码生成/平台文档/接口文档
INSERT INTO `sys_menu` VALUES (1, 0, '0', '系统管理', 'C', '', '/system', 'Layout', NULL, NULL, NULL, 1, 1, 'system', '/system/user', now(), now(), NULL);
INSERT INTO `sys_menu` VALUES (2, 0, '0', '代码生成', 'C', '', '/codegen', 'Layout', NULL, NULL, NULL, 1, 2, 'code', '/codegen/index', now(), now(), NULL);
INSERT INTO `sys_menu` VALUES (4, 0, '0', '平台文档', 'C', '', '/doc', 'Layout', NULL, NULL, NULL, 1, 4, 'document', '', now(), now(), NULL);
INSERT INTO `sys_menu` VALUES (5, 0, '0', '接口文档', 'C', '', '/api', 'Layout', NULL, NULL, NULL, 1, 5, 'api', '', now(), now(), NULL);
-- 系统管理
INSERT INTO `sys_menu` VALUES (210, 1, '0,1', '用户管理', 'M', 'User', 'user', 'system/user/index', NULL, NULL, 1, 1, 1, 'el-icon-User', NULL, now(), now(), NULL);
INSERT INTO `sys_menu` VALUES (2101, 210, '0,1,210', '用户查询', 'B', NULL, '', NULL, 'sys:user:list', NULL, NULL, 1, 1, '', NULL, now(), now(), NULL);
INSERT INTO `sys_menu` VALUES (2102, 210, '0,1,210', '用户新增', 'B', NULL, '', NULL, 'sys:user:create', NULL, NULL, 1, 2, '', NULL, now(), now(), NULL);
INSERT INTO `sys_menu` VALUES (2103, 210, '0,1,210', '用户编辑', 'B', NULL, '', NULL, 'sys:user:update', NULL, NULL, 1, 3, '', NULL, now(), now(), NULL);
INSERT INTO `sys_menu` VALUES (2104, 210, '0,1,210', '用户删除', 'B', NULL, '', NULL, 'sys:user:delete', NULL, NULL, 1, 4, '', NULL, now(), now(), NULL);
INSERT INTO `sys_menu` VALUES (2105, 210, '0,1,210', '重置密码', 'B', NULL, '', NULL, 'sys:user:reset-password', NULL, NULL, 1, 5, '', NULL, now(), now(), NULL);
INSERT INTO `sys_menu` VALUES (2106, 210, '0,1,210', '用户导入', 'B', NULL, '', NULL, 'sys:user:import', NULL, NULL, 1, 6, '', NULL, now(), now(), NULL);
INSERT INTO `sys_menu` VALUES (2107, 210, '0,1,210', '用户导出', 'B', NULL, '', NULL, 'sys:user:export', NULL, NULL, 1, 7, '', NULL, now(), now(), NULL);
INSERT INTO `sys_menu` VALUES (220, 1, '0,1', '角色管理', 'M', 'Role', 'role', 'system/role/index', NULL, NULL, 1, 1, 2, 'role', NULL, now(), now(), NULL);
INSERT INTO `sys_menu` VALUES (2201, 220, '0,1,220', '角色查询', 'B', NULL, '', NULL, 'sys:role:list', NULL, NULL, 1, 1, '', NULL, now(), now(), NULL);
INSERT INTO `sys_menu` VALUES (2202, 220, '0,1,220', '角色新增', 'B', NULL, '', NULL, 'sys:role:create', NULL, NULL, 1, 2, '', NULL, now(), now(), NULL);
INSERT INTO `sys_menu` VALUES (2203, 220, '0,1,220', '角色编辑', 'B', NULL, '', NULL, 'sys:role:update', NULL, NULL, 1, 3, '', NULL, now(), now(), NULL);
INSERT INTO `sys_menu` VALUES (2204, 220, '0,1,220', '角色删除', 'B', NULL, '', NULL, 'sys:role:delete', NULL, NULL, 1, 4, '', NULL, now(), now(), NULL);
INSERT INTO `sys_menu` VALUES (2205, 220, '0,1,220', '角色分配权限', 'B', NULL, '', NULL, 'sys:role:assign', NULL, NULL, 1, 5, '', NULL, now(), now(), NULL);
INSERT INTO `sys_menu` VALUES (230, 1, '0,1', '菜单管理', 'M', 'SysMenu', 'menu', 'system/menu/index', NULL, NULL, 1, 1, 3, 'menu', NULL, now(), now(), NULL);
INSERT INTO `sys_menu` VALUES (2301, 230, '0,1,230', '菜单查询', 'B', NULL, '', NULL, 'sys:menu:list', NULL, NULL, 1, 1, '', NULL, now(), now(), NULL);
INSERT INTO `sys_menu` VALUES (2302, 230, '0,1,230', '菜单新增', 'B', NULL, '', NULL, 'sys:menu:create', NULL, NULL, 1, 2, '', NULL, now(), now(), NULL);
INSERT INTO `sys_menu` VALUES (2303, 230, '0,1,230', '菜单编辑', 'B', NULL, '', NULL, 'sys:menu:update', NULL, NULL, 1, 3, '', NULL, now(), now(), NULL);
INSERT INTO `sys_menu` VALUES (2304, 230, '0,1,230', '菜单删除', 'B', NULL, '', NULL, 'sys:menu:delete', NULL, NULL, 1, 4, '', NULL, now(), now(), NULL);
INSERT INTO `sys_menu` VALUES (240, 1, '0,1', '部门管理', 'M', 'Dept', 'dept', 'system/dept/index', NULL, NULL, 1, 1, 4, 'tree', NULL, now(), now(), NULL);
INSERT INTO `sys_menu` VALUES (2401, 240, '0,1,240', '部门查询', 'B', NULL, '', NULL, 'sys:dept:list', NULL, NULL, 1, 1, '', NULL, now(), now(), NULL);
INSERT INTO `sys_menu` VALUES (2402, 240, '0,1,240', '部门新增', 'B', NULL, '', NULL, 'sys:dept:create', NULL, NULL, 1, 2, '', NULL, now(), now(), NULL);
INSERT INTO `sys_menu` VALUES (2403, 240, '0,1,240', '部门编辑', 'B', NULL, '', NULL, 'sys:dept:update', NULL, NULL, 1, 3, '', NULL, now(), now(), NULL);
INSERT INTO `sys_menu` VALUES (2404, 240, '0,1,240', '部门删除', 'B', NULL, '', NULL, 'sys:dept:delete', NULL, NULL, 1, 4, '', NULL, now(), now(), NULL);
INSERT INTO `sys_menu` VALUES (250, 1, '0,1', '字典管理', 'M', 'Dict', 'dict', 'system/dict/index', NULL, NULL, 1, 1, 5, 'dict', NULL, now(), now(), NULL);
INSERT INTO `sys_menu` VALUES (2501, 250, '0,1,250', '字典查询', 'B', NULL, '', NULL, 'sys:dict:list', NULL, NULL, 1, 1, '', NULL, now(), now(), NULL);
INSERT INTO `sys_menu` VALUES (2502, 250, '0,1,250', '字典新增', 'B', NULL, '', NULL, 'sys:dict:create', NULL, NULL, 1, 2, '', NULL, now(), now(), NULL);
INSERT INTO `sys_menu` VALUES (2503, 250, '0,1,250', '字典编辑', 'B', NULL, '', NULL, 'sys:dict:update', NULL, NULL, 1, 3, '', NULL, now(), now(), NULL);
INSERT INTO `sys_menu` VALUES (2504, 250, '0,1,250', '字典删除', 'B', NULL, '', NULL, 'sys:dict:delete', NULL, NULL, 1, 4, '', NULL, now(), now(), NULL);
INSERT INTO `sys_menu` VALUES (251, 1, '0,1', '字典项', 'M', 'DictItem', 'dict-item', 'system/dict/dict-item', NULL, 0, 1, 0, 6, '', NULL, now(), now(), NULL);
INSERT INTO `sys_menu` VALUES (2511, 251, '0,1,251', '字典项查询', 'B', NULL, '', NULL, 'sys:dict-item:list', NULL, NULL, 1, 1, '', NULL, now(), now(), NULL);
INSERT INTO `sys_menu` VALUES (2512, 251, '0,1,251', '字典项新增', 'B', NULL, '', NULL, 'sys:dict-item:create', NULL, NULL, 1, 2, '', NULL, now(), now(), NULL);
INSERT INTO `sys_menu` VALUES (2513, 251, '0,1,251', '字典项编辑', 'B', NULL, '', NULL, 'sys:dict-item:update', NULL, NULL, 1, 3, '', NULL, now(), now(), NULL);
INSERT INTO `sys_menu` VALUES (2514, 251, '0,1,251', '字典项删除', 'B', NULL, '', NULL, 'sys:dict-item:delete', NULL, NULL, 1, 4, '', NULL, now(), now(), NULL);
INSERT INTO `sys_menu` VALUES (260, 1, '0,1', '系统日志', 'M', 'Log', 'log', 'system/log/index', NULL, 0, 1, 1, 7, 'document', NULL, now(), now(), NULL);
INSERT INTO `sys_menu` VALUES (270, 1, '0,1', '系统配置', 'M', 'Config', 'config', 'system/config/index', NULL, 0, 1, 1, 8, 'setting', NULL, now(), now(), NULL);
INSERT INTO `sys_menu` VALUES (2701, 270, '0,1,270', '系统配置查询', 'B', NULL, '', NULL, 'sys:config:list', 0, 1, 1, 1, '', NULL, now(), now(), NULL);
INSERT INTO `sys_menu` VALUES (2702, 270, '0,1,270', '系统配置新增', 'B', NULL, '', NULL, 'sys:config:create', 0, 1, 1, 2, '', NULL, now(), now(), NULL);
INSERT INTO `sys_menu` VALUES (2703, 270, '0,1,270', '系统配置修改', 'B', NULL, '', NULL, 'sys:config:update', 0, 1, 1, 3, '', NULL, now(), now(), NULL);
INSERT INTO `sys_menu` VALUES (2704, 270, '0,1,270', '系统配置删除', 'B', NULL, '', NULL, 'sys:config:delete', 0, 1, 1, 4, '', NULL, now(), now(), NULL);
INSERT INTO `sys_menu` VALUES (2705, 270, '0,1,270', '系统配置刷新', 'B', NULL, '', NULL, 'sys:config:refresh', 0, 1, 1, 5, '', NULL, now(), now(), NULL);
INSERT INTO `sys_menu` VALUES (280, 1, '0,1', '通知公告', 'M', 'Notice', 'notice', 'system/notice/index', NULL, NULL, NULL, 1, 9, '', NULL, now(), now(), NULL);
INSERT INTO `sys_menu` VALUES (2801, 280, '0,1,280', '通知查询', 'B', NULL, '', NULL, 'sys:notice:list', NULL, NULL, 1, 1, '', NULL, now(), now(), NULL);
INSERT INTO `sys_menu` VALUES (2802, 280, '0,1,280', '通知新增', 'B', NULL, '', NULL, 'sys:notice:create', NULL, NULL, 1, 2, '', NULL, now(), now(), NULL);
INSERT INTO `sys_menu` VALUES (2803, 280, '0,1,280', '通知编辑', 'B', NULL, '', NULL, 'sys:notice:update', NULL, NULL, 1, 3, '', NULL, now(), now(), NULL);
INSERT INTO `sys_menu` VALUES (2804, 280, '0,1,280', '通知删除', 'B', NULL, '', NULL, 'sys:notice:delete', NULL, NULL, 1, 4, '', NULL, now(), now(), NULL);
INSERT INTO `sys_menu` VALUES (2805, 280, '0,1,280', '通知发布', 'B', NULL, '', NULL, 'sys:notice:publish', 0, 1, 1, 5, '', NULL, now(), now(), NULL);
INSERT INTO `sys_menu` VALUES (2806, 280, '0,1,280', '通知撤回', 'B', NULL, '', NULL, 'sys:notice:revoke', 0, 1, 1, 6, '', NULL, now(), now(), NULL);
-- 代码生成
INSERT INTO `sys_menu` VALUES (310, 2, '0,2', '代码生成', 'M', 'Codegen', 'codegen', 'codegen/index', NULL, NULL, 1, 1, 1, 'code', NULL, now(), now(), NULL);
-- 平台文档(外链通过 route_path 识别)
INSERT INTO `sys_menu` VALUES (501, 4, '0,4', '平台文档(外链)', 'M', NULL, 'https://juejin.cn/post/7228990409909108793', '', NULL, NULL, NULL, 1, 1, 'document', '', now(), now(), NULL);
INSERT INTO `sys_menu` VALUES (502, 4, '0,4', '后端文档', 'M', NULL, 'https://youlai.blog.csdn.net/article/details/145178880', '', NULL, NULL, NULL, 1, 2, 'document', '', now(), now(), NULL);
INSERT INTO `sys_menu` VALUES (503, 4, '0,4', '移动端文档', 'M', NULL, 'https://youlai.blog.csdn.net/article/details/143222890', '', NULL, NULL, NULL, 1, 3, 'document', '', now(), now(), NULL);
INSERT INTO `sys_menu` VALUES (504, 4, '0,4', '内部文档', 'M', NULL, 'internal-doc', 'demo/internal-doc', NULL, NULL, NULL, 1, 4, 'document', '', now(), now(), NULL);
-- 接口文档
INSERT INTO `sys_menu` VALUES (601, 5, '0,5', 'Apifox', 'M', 'Apifox', 'apifox', 'demo/api/apifox', NULL, NULL, 1, 1, 1, 'api', '', now(), now(), NULL);
-- ----------------------------
-- Table structure for sys_role
-- ----------------------------
DROP TABLE IF EXISTS `sys_role`;
CREATE TABLE `sys_role` (
`id` bigint NOT NULL AUTO_INCREMENT,
`name` varchar(64) NOT NULL COMMENT '角色名称',
`code` varchar(32) NOT NULL COMMENT '角色编码',
`sort` int NULL COMMENT '显示顺序',
`status` tinyint(1) DEFAULT 1 COMMENT '角色状态(1-正常 0-停用)',
`data_scope` tinyint NULL COMMENT '数据权限(1-所有数据 2-部门及子部门数据 3-本部门数据 4-本人数据 5-自定义部门数据)',
`create_by` bigint NULL COMMENT '创建人 ID',
`create_time` datetime NULL COMMENT '创建时间',
`update_by` bigint NULL COMMENT '更新人ID',
`update_time` datetime NULL COMMENT '更新时间',
`is_deleted` tinyint(1) DEFAULT 0 COMMENT '逻辑删除标识(0-未删除 1-已删除)',
PRIMARY KEY (`id`) USING BTREE,
UNIQUE INDEX `uk_name`(`name` ASC) USING BTREE COMMENT '角色名称唯一索引',
UNIQUE INDEX `uk_code`(`code` ASC) USING BTREE COMMENT '角色编码唯一索引'
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COMMENT = '系统角色表';
-- ----------------------------
-- Records of sys_role
-- ----------------------------
INSERT INTO `sys_role` VALUES (1, '超级管理员', 'ROOT', 1, 1, 1, NULL, now(), NULL, now(), 0);
INSERT INTO `sys_role` VALUES (2, '系统管理员', 'ADMIN', 2, 1, 1, NULL, now(), NULL, NULL, 0);
INSERT INTO `sys_role` VALUES (3, '访问游客', 'GUEST', 3, 1, 3, NULL, now(), NULL, now(), 0);
INSERT INTO `sys_role` VALUES (4, '部门主管', 'DEPT_MANAGER', 4, 1, 2, NULL, now(), NULL, now(), 0);
INSERT INTO `sys_role` VALUES (5, '部门成员', 'DEPT_MEMBER', 5, 1, 3, NULL, now(), NULL, now(), 0);
INSERT INTO `sys_role` VALUES (6, '普通员工', 'EMPLOYEE', 6, 1, 4, NULL, now(), NULL, now(), 0);
INSERT INTO `sys_role` VALUES (7, '自定义权限用户', 'CUSTOM_USER', 7, 1, 5, NULL, now(), NULL, now(), 0);
-- ----------------------------
-- Table structure for sys_role_menu
-- ----------------------------
DROP TABLE IF EXISTS `sys_role_menu`;
CREATE TABLE `sys_role_menu` (
`role_id` bigint NOT NULL COMMENT '角色ID',
`menu_id` bigint NOT NULL COMMENT '菜单ID',
UNIQUE INDEX `uk_roleid_menuid`(`role_id` ASC, `menu_id` ASC) USING BTREE COMMENT '角色菜单唯一索引'
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COMMENT = '角色菜单关联表';
-- ----------------------------
-- Table structure for sys_role_dept
-- ----------------------------
DROP TABLE IF EXISTS `sys_role_dept`;
CREATE TABLE `sys_role_dept` (
`role_id` bigint NOT NULL COMMENT '角色ID',
`dept_id` bigint NOT NULL COMMENT '部门ID',
UNIQUE INDEX `uk_roleid_deptid`(`role_id` ASC, `dept_id` ASC) USING BTREE COMMENT '角色部门唯一索引'
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COMMENT = '角色部门关联表';
-- ----------------------------
-- Records of sys_role_dept
-- ----------------------------
INSERT IGNORE INTO `sys_role_dept` VALUES (7, 1);
INSERT IGNORE INTO `sys_role_dept` VALUES (7, 2);
-- ============================================
-- 系统管理员角色菜单权限(role_id=2)
-- 顶级目录
INSERT INTO `sys_role_menu` VALUES (2, 1), (2, 2), (2, 4), (2, 5);
-- 系统管理
INSERT INTO `sys_role_menu` VALUES (2, 210), (2, 2101), (2, 2102), (2, 2103), (2, 2104), (2, 2105), (2, 2106), (2, 2107);
INSERT INTO `sys_role_menu` VALUES (2, 220), (2, 2201), (2, 2202), (2, 2203), (2, 2204), (2, 2205);
INSERT INTO `sys_role_menu` VALUES (2, 230), (2, 2301), (2, 2302), (2, 2303), (2, 2304);
INSERT INTO `sys_role_menu` VALUES (2, 240), (2, 2401), (2, 2402), (2, 2403), (2, 2404);
INSERT INTO `sys_role_menu` VALUES (2, 250), (2, 2501), (2, 2502), (2, 2503), (2, 2504);
INSERT INTO `sys_role_menu` VALUES (2, 251), (2, 2511), (2, 2512), (2, 2513), (2, 2514);
INSERT INTO `sys_role_menu` VALUES (2, 260), (2, 2601);
INSERT INTO `sys_role_menu` VALUES (2, 270), (2, 2701), (2, 2702), (2, 2703), (2, 2704), (2, 2705);
INSERT INTO `sys_role_menu` VALUES (2, 280), (2, 2801), (2, 2802), (2, 2803), (2, 2804), (2, 2805), (2, 2806);
INSERT IGNORE INTO `sys_role_menu` VALUES (4, 1);
INSERT IGNORE INTO `sys_role_menu` VALUES (4, 210), (4, 2101), (4, 2102), (4, 2103), (4, 2104), (4, 2105), (4, 2106), (4, 2107);
INSERT IGNORE INTO `sys_role_menu` VALUES (4, 220), (4, 2201), (4, 2202), (4, 2203), (4, 2204), (4, 2205);
INSERT IGNORE INTO `sys_role_menu` VALUES (5, 1);
INSERT IGNORE INTO `sys_role_menu` VALUES (5, 210), (5, 2101), (5, 2102), (5, 2103), (5, 2104), (5, 2105), (5, 2106), (5, 2107);
INSERT IGNORE INTO `sys_role_menu` VALUES (5, 220), (5, 2201), (5, 2202), (5, 2203), (5, 2204), (5, 2205);
INSERT IGNORE INTO `sys_role_menu` VALUES (6, 1);
INSERT IGNORE INTO `sys_role_menu` VALUES (6, 210), (6, 2101), (6, 2102), (6, 2103), (6, 2104), (6, 2105), (6, 2106), (6, 2107);
INSERT IGNORE INTO `sys_role_menu` VALUES (6, 220), (6, 2201), (6, 2202), (6, 2203), (6, 2204), (6, 2205);
INSERT IGNORE INTO `sys_role_menu` VALUES (7, 1);
INSERT IGNORE INTO `sys_role_menu` VALUES (7, 210), (7, 2101), (7, 2102), (7, 2103), (7, 2104), (7, 2105), (7, 2106), (7, 2107);
INSERT IGNORE INTO `sys_role_menu` VALUES (7, 220), (7, 2201), (7, 2202), (7, 2203), (7, 2204), (7, 2205);
-- 代码生成
INSERT INTO `sys_role_menu` VALUES (2, 310);
-- 平台文档
INSERT INTO `sys_role_menu` VALUES (2, 501), (2, 502), (2, 503), (2, 504);
-- 接口文档
INSERT INTO `sys_role_menu` VALUES (2, 601);
-- ----------------------------
-- Table structure for sys_user
-- ----------------------------
DROP TABLE IF EXISTS `sys_user`;
CREATE TABLE `sys_user` (
`id` bigint NOT NULL AUTO_INCREMENT,
`username` varchar(64) COMMENT '用户名',
`nickname` varchar(64) COMMENT '昵称',
`gender` tinyint(1) DEFAULT 1 COMMENT '性别((1-男 2-女 0-保密)',
`password` varchar(100) COMMENT '密码',
`dept_id` int COMMENT '部门ID',
`avatar` varchar(255) COMMENT '用户头像',
`mobile` varchar(20) COMMENT '联系方式',
`status` tinyint(1) DEFAULT 1 COMMENT '状态(1-正常 0-禁用)',
`email` varchar(128) COMMENT '用户邮箱',
`create_time` datetime COMMENT '创建时间',
`create_by` bigint COMMENT '创建人ID',
`update_time` datetime COMMENT '更新时间',
`update_by` bigint COMMENT '修改人ID',
`is_deleted` tinyint(1) DEFAULT 0 COMMENT '逻辑删除标识(0-未删除 1-已删除)',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COMMENT = '系统用户表';
-- ----------------------------
-- Records of sys_user
-- ----------------------------
INSERT INTO `sys_user` VALUES (1, 'root', '有来技术', 0, '$2a$10$xVWsNOhHrCxh5UbpCE7/HuJ.PAOKcYAqRxD2CO2nVnJS.IAXkr5aq', NULL, 'https://foruda.gitee.com/images/1723603502796844527/03cdca2a_716974.gif', '18812345677', 1, 'youlaitech@163.com', now(), NULL, now(), NULL, 0);
INSERT INTO `sys_user` VALUES (2, 'admin', '系统管理员', 1, '$2a$10$xVWsNOhHrCxh5UbpCE7/HuJ.PAOKcYAqRxD2CO2nVnJS.IAXkr5aq', 1, 'https://foruda.gitee.com/images/1723603502796844527/03cdca2a_716974.gif', '18888888888', 1, 'youlaitech@163.com', now(), NULL, now(), NULL, 0);
INSERT INTO `sys_user` VALUES (3, 'test', '测试小用户', 1, '$2a$10$xVWsNOhHrCxh5UbpCE7/HuJ.PAOKcYAqRxD2CO2nVnJS.IAXkr5aq', 3, 'https://foruda.gitee.com/images/1723603502796844527/03cdca2a_716974.gif', '18812345679', 1, 'youlaitech@163.com', now(), NULL, now(), NULL, 0);
INSERT INTO `sys_user` VALUES (4, 'dept_manager', '部门主管', 1, '$2a$10$xVWsNOhHrCxh5UbpCE7/HuJ.PAOKcYAqRxD2CO2nVnJS.IAXkr5aq', 1, 'https://foruda.gitee.com/images/1723603502796844527/03cdca2a_716974.gif', '18812345680', 1, 'manager@youlaitech.com', now(), NULL, now(), NULL, 0);
INSERT INTO `sys_user` VALUES (5, 'dept_member', '部门成员', 1, '$2a$10$xVWsNOhHrCxh5UbpCE7/HuJ.PAOKcYAqRxD2CO2nVnJS.IAXkr5aq', 1, 'https://foruda.gitee.com/images/1723603502796844527/03cdca2a_716974.gif', '18812345681', 1, 'member@youlaitech.com', now(), NULL, now(), NULL, 0);
INSERT INTO `sys_user` VALUES (6, 'employee', '普通员工', 1, '$2a$10$xVWsNOhHrCxh5UbpCE7/HuJ.PAOKcYAqRxD2CO2nVnJS.IAXkr5aq', 2, 'https://foruda.gitee.com/images/1723603502796844527/03cdca2a_716974.gif', '18812345682', 1, 'employee@youlaitech.com', now(), NULL, now(), NULL, 0);
INSERT INTO `sys_user` VALUES (7, 'custom_user', '自定义权限用户', 1, '$2a$10$xVWsNOhHrCxh5UbpCE7/HuJ.PAOKcYAqRxD2CO2nVnJS.IAXkr5aq', 3, 'https://foruda.gitee.com/images/1723603502796844527/03cdca2a_716974.gif', '18812345683', 1, 'custom@youlaitech.com', now(), NULL, now(), NULL, 0);
-- ----------------------------
-- Table structure for sys_user_role
-- ----------------------------
DROP TABLE IF EXISTS `sys_user_role`;
CREATE TABLE `sys_user_role` (
`user_id` bigint NOT NULL COMMENT '用户ID',
`role_id` bigint NOT NULL COMMENT '角色ID',
PRIMARY KEY (`user_id`, `role_id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COMMENT = '用户角色关联表';
-- ----------------------------
-- Records of sys_user_role
-- ----------------------------
INSERT INTO `sys_user_role` VALUES (1, 1);
INSERT INTO `sys_user_role` VALUES (2, 2);
INSERT INTO `sys_user_role` VALUES (3, 3);
INSERT IGNORE INTO `sys_user_role` VALUES (4, 4);
INSERT IGNORE INTO `sys_user_role` VALUES (5, 5);
INSERT IGNORE INTO `sys_user_role` VALUES (6, 6);
INSERT IGNORE INTO `sys_user_role` VALUES (7, 7);
-- ----------------------------
-- Table structure for sys_log
-- ----------------------------
DROP TABLE IF EXISTS `sys_log`;
CREATE TABLE `sys_log` (
`id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键',
`module` TINYINT NOT NULL COMMENT '模块,数字枚举,参考 LogModule 枚举',
`action_type` TINYINT NOT NULL COMMENT '操作类型,数字枚举,参考 ActionType 枚举',
`title` VARCHAR(100) NOT NULL COMMENT '前端显示标题',
`content` TEXT COMMENT '自定义日志内容',
`operator_id` BIGINT COMMENT '操作人ID',
`operator_name` VARCHAR(50) COMMENT '操作人名称',
`request_uri` VARCHAR(255) COMMENT '请求路径',
`request_method` VARCHAR(10) COMMENT '请求方法',
`ip` VARCHAR(45) COMMENT 'IP地址',
`province` VARCHAR(100) COMMENT '省份',
`city` VARCHAR(100) COMMENT '城市',
`device` VARCHAR(100) COMMENT '设备',
`os` VARCHAR(100) COMMENT '操作系统',
`browser` VARCHAR(100) COMMENT '浏览器',
`status` TINYINT DEFAULT 1 COMMENT '0失败 1成功',
`error_msg` VARCHAR(255) COMMENT '错误信息',
`execution_time` INT COMMENT '执行时间(ms)',
`create_time` DATETIME COMMENT '操作时间',
PRIMARY KEY (`id`) USING BTREE,
KEY `idx_module_action_time` (`module`, `action_type`, `create_time`),
KEY `idx_operator_time` (`operator_id`, `create_time`),
KEY `idx_time` (`create_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='系统操作日志表';
-- ----------------------------
-- Table structure for gen_table
-- ----------------------------
DROP TABLE IF EXISTS `gen_table`;
CREATE TABLE `gen_table` (
`id` bigint NOT NULL AUTO_INCREMENT,
`table_name` varchar(100) NOT NULL COMMENT '表名',
`module_name` varchar(100) COMMENT '模块名',
`package_name` varchar(255) NOT NULL COMMENT '包名',
`business_name` varchar(100) NOT NULL COMMENT '业务名',
`entity_name` varchar(100) NOT NULL COMMENT '实体类名',
`author` varchar(50) NOT NULL COMMENT '作者',
`parent_menu_id` bigint COMMENT '上级菜单ID,对应sys_menu的id ',
`remove_table_prefix` varchar(20) COMMENT '要移除的表前缀,如: sys_',
`page_type` varchar(20) COMMENT '页面类型(classic|curd)',
`create_time` datetime COMMENT '创建时间',
`update_time` datetime COMMENT '更新时间',
`is_deleted` tinyint(4) DEFAULT 0 COMMENT '是否删除',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_tablename` (`table_name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='代码生成配置表';
-- ----------------------------
-- Table structure for gen_table_column
-- ----------------------------
DROP TABLE IF EXISTS `gen_table_column`;
CREATE TABLE `gen_table_column` (
`id` bigint NOT NULL AUTO_INCREMENT,
`table_id` bigint NOT NULL COMMENT '关联的表配置ID',
`column_name` varchar(100) ,
`column_type` varchar(50) ,
`column_length` int ,
`field_name` varchar(100) NOT NULL COMMENT '字段名称',
`field_type` varchar(100) COMMENT '字段类型',
`field_sort` int COMMENT '字段排序',
`field_comment` varchar(255) COMMENT '字段描述',
`max_length` int ,
`is_required` tinyint(1) COMMENT '是否必填',
`is_show_in_list` tinyint(1) DEFAULT '0' COMMENT '是否在列表显示',
`is_show_in_form` tinyint(1) DEFAULT '0' COMMENT '是否在表单显示',
`is_show_in_query` tinyint(1) DEFAULT '0' COMMENT '是否在查询条件显示',
`query_type` tinyint COMMENT '查询方式',
`form_type` tinyint COMMENT '表单类型',
`dict_type` varchar(50) COMMENT '字典类型',
`create_time` datetime COMMENT '创建时间',
`update_time` datetime COMMENT '更新时间',
PRIMARY KEY (`id`),
KEY `idx_table_id` (`table_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='代码生成字段配置表';
-- ----------------------------
-- 系统配置表
-- ----------------------------
DROP TABLE IF EXISTS `sys_config`;
CREATE TABLE `sys_config` (
`id` bigint NOT NULL AUTO_INCREMENT,
`config_name` varchar(50) NOT NULL COMMENT '配置名称',
`config_key` varchar(50) NOT NULL COMMENT '配置key',
`config_value` text NOT NULL COMMENT '配置值',
`remark` varchar(255) COMMENT '备注',
`create_time` datetime COMMENT '创建时间',
`create_by` bigint COMMENT '创建人ID',
`update_time` datetime COMMENT '更新时间',
`update_by` bigint COMMENT '更新人ID',
`is_deleted` tinyint(4) DEFAULT '0' NOT NULL COMMENT '逻辑删除标识(0-未删除 1-已删除)',
PRIMARY KEY (`id`)
) ENGINE=InnoDB COMMENT='系统配置表';
INSERT INTO `sys_config` VALUES (1, '系统限流QPS', 'IP_QPS_THRESHOLD_LIMIT', '10', '单个IP请求的最大每秒查询数(QPS)阈值Key', now(), 1, NULL, NULL, 0);
-- ----------------------------
-- 通知公告表
-- ----------------------------
DROP TABLE IF EXISTS `sys_notice`;
CREATE TABLE `sys_notice` (
`id` bigint NOT NULL AUTO_INCREMENT,
`title` varchar(50) COMMENT '通知标题',
`content` text COMMENT '通知内容',
`type` tinyint NOT NULL COMMENT '通知类型(关联字典编码:notice_type',
`level` varchar(5) NOT NULL COMMENT '通知等级(字典codenotice_level',
`target_type` tinyint NOT NULL COMMENT '目标类型(1: 全体, 2: 指定)',
`target_user_ids` varchar(255) COMMENT '目标人ID集合(多个使用英文逗号,分割)',
`publisher_id` bigint COMMENT '发布人ID',
`publish_status` tinyint DEFAULT '0' COMMENT '发布状态(0: 未发布, 1: 已发布, -1: 已撤回)',
`publish_time` datetime COMMENT '发布时间',
`revoke_time` datetime COMMENT '撤回时间',
`create_by` bigint NOT NULL COMMENT '创建人ID',
`create_time` datetime NOT NULL COMMENT '创建时间',
`update_by` bigint COMMENT '更新人ID',
`update_time` datetime COMMENT '更新时间',
`is_deleted` tinyint(1) DEFAULT '0' COMMENT '是否删除(0: 未删除, 1: 已删除)',
PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='系统通知公告表';
INSERT INTO `sys_notice` VALUES (1, 'v3.0.0 版本发布 - 多租户功能上线', '<p>🎉 新版本发布,主要更新内容:</p><p>1. 新增多租户功能,支持租户隔离和数据管理</p><p>2. 优化系统性能,提升响应速度</p><p>3. 完善权限管理,增强安全性</p><p>4. 修复已知问题,提升系统稳定性</p>', 1, 'H', 1, NULL, 1, 1, '2024-12-15 10:00:00', NULL, 1, '2024-12-15 10:00:00', 1, '2024-12-15 10:00:00', 0);
INSERT INTO `sys_notice` VALUES (2, '系统维护通知 - 2024年12月20日', '<p>⏰ 系统维护通知</p><p>系统将于 <strong>2024年12月20日(本周五)凌晨 2:00-4:00</strong> 进行例行维护升级。</p><p>维护期间系统将暂停服务,请提前做好数据备份工作。</p><p>给您带来的不便,敬请谅解!</p>', 2, 'H', 1, NULL, 1, 1, '2024-12-18 14:30:00', NULL, 1, '2024-12-18 14:30:00', 1, '2024-12-18 14:30:00', 0);
INSERT INTO `sys_notice` VALUES (3, '安全提醒 - 防范钓鱼邮件', '<p>⚠️ 安全提醒</p><p>近期发现有不法分子通过钓鱼邮件进行网络攻击,请大家提高警惕:</p><p>1. 不要点击来源不明的邮件链接</p><p>2. 不要下载可疑附件</p><p>3. 遇到可疑邮件请及时联系IT部门</p><p>4. 定期修改密码,使用强密码策略</p>', 3, 'H', 1, NULL, 1, 1, '2024-12-10 09:00:00', NULL, 1, '2024-12-10 09:00:00', 1, '2024-12-10 09:00:00', 0);
INSERT INTO `sys_notice` VALUES (4, '元旦假期安排通知', '<p>📅 元旦假期安排</p><p>根据国家法定节假日安排,公司元旦假期时间为:</p><p><strong>2024年12月30日(周一)至 2025年1月1日(周三)</strong>,共3天。</p><p>2024年12月29日(周日)正常上班。</p><p>祝大家元旦快乐,假期愉快!</p>', 4, 'M', 1, NULL, 1, 1, '2024-12-25 16:00:00', NULL, 1, '2024-12-25 16:00:00', 1, '2024-12-25 16:00:00', 0);
INSERT INTO `sys_notice` VALUES (5, '新产品发布会邀请', '<p>🎊 新产品发布会邀请</p><p>公司将于 <strong>2025年1月15日下午14:00</strong> 在总部会议室举办新产品发布会。</p><p>届时将展示最新研发的产品和技术成果,欢迎全体员工参加。</p><p>请各部门提前安排好工作,准时参加。</p>', 5, 'M', 1, NULL, 1, 1, '2024-12-28 11:00:00', NULL, 1, '2024-12-28 11:00:00', 1, '2024-12-28 11:00:00', 0);
INSERT INTO `sys_notice` VALUES (6, 'v2.16.1 版本更新', '<p>✨ 版本更新</p><p>v2.16.1 版本已发布,主要修复内容:</p><p>1. 修复 WebSocket 重复连接导致的后台线程阻塞问题</p><p>2. 优化通知公告功能,提升用户体验</p><p>3. 修复部分已知bug</p><p>建议尽快更新到最新版本。</p>', 1, 'M', 1, NULL, 1, 1, '2024-12-05 15:30:00', NULL, 1, '2024-12-05 15:30:00', 1, '2024-12-05 15:30:00', 0);
INSERT INTO `sys_notice` VALUES (7, '年终总结会议通知', '<p>📋 年终总结会议通知</p><p>各部门年终总结会议将于 <strong>2024年12月30日上午9:00</strong> 召开。</p><p>请各部门负责人提前准备好年度工作总结和下年度工作计划。</p><p>会议地点:总部大会议室</p>', 5, 'M', 2, '1,2', 1, 1, '2024-12-22 10:00:00', NULL, 1, '2024-12-22 10:00:00', 1, '2024-12-22 10:00:00', 0);
INSERT INTO `sys_notice` VALUES (8, '系统功能优化完成', '<p>✅ 系统功能优化</p><p>已完成以下功能优化:</p><p>1. 优化用户管理界面,提升操作体验</p><p>2. 增强数据导出功能,支持更多格式</p><p>3. 优化搜索功能,提升查询效率</p><p>4. 修复部分界面显示问题</p>', 1, 'L', 1, NULL, 1, 1, '2024-12-12 14:20:00', NULL, 1, '2024-12-12 14:20:00', 1, '2024-12-12 14:20:00', 0);
INSERT INTO `sys_notice` VALUES (9, '员工培训计划', '<p>📚 员工培训计划</p><p>为提升员工专业技能,公司将于 <strong>2025年1月8日-10日</strong> 组织技术培训。</p><p>培训内容:</p><p>1. 新技术框架应用</p><p>2. 代码规范与最佳实践</p><p>3. 系统架构设计</p><p>请各部门合理安排工作,确保培训顺利进行。</p>', 5, 'M', 1, NULL, 1, 1, '2024-12-20 09:30:00', NULL, 1, '2024-12-20 09:30:00', 1, '2024-12-20 09:30:00', 0);
INSERT INTO `sys_notice` VALUES (10, '数据备份提醒', '<p>💾 数据备份提醒</p><p>请各部门注意定期备份重要数据,建议每周至少备份一次。</p><p>备份方式:</p><p>1. 使用系统自带备份功能</p><p>2. 手动导出重要数据</p><p>3. 联系IT部门协助备份</p><p>数据安全,人人有责!</p>', 3, 'L', 1, NULL, 1, 1, '2024-12-08 08:00:00', NULL, 1, '2024-12-08 08:00:00', 1, '2024-12-08 08:00:00', 0);
-- ----------------------------
-- 用户通知公告表
-- ----------------------------
DROP TABLE IF EXISTS `sys_user_notice`;
CREATE TABLE `sys_user_notice` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT 'id',
`notice_id` bigint NOT NULL COMMENT '公共通知id',
`user_id` bigint NOT NULL COMMENT '用户id',
`is_read` bigint DEFAULT '0' COMMENT '读取状态(0: 未读, 1: 已读)',
`read_time` datetime COMMENT '阅读时间',
`create_time` datetime NOT NULL COMMENT '创建时间',
`update_time` datetime COMMENT '更新时间',
`is_deleted` tinyint DEFAULT '0' COMMENT '逻辑删除(0: 未删除, 1: 已删除)',
PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户通知公告关联表';
INSERT INTO `sys_user_notice` VALUES (1, 1, 2, 1, NULL, now(), now(), 0);
INSERT INTO `sys_user_notice` VALUES (2, 2, 2, 1, NULL, now(), now(), 0);
INSERT INTO `sys_user_notice` VALUES (3, 3, 2, 1, NULL, now(), now(), 0);
INSERT INTO `sys_user_notice` VALUES (4, 4, 2, 1, NULL, now(), now(), 0);
INSERT INTO `sys_user_notice` VALUES (5, 5, 2, 1, NULL, now(), now(), 0);
INSERT INTO `sys_user_notice` VALUES (6, 6, 2, 1, NULL, now(), now(), 0);
INSERT INTO `sys_user_notice` VALUES (7, 7, 2, 1, NULL, now(), now(), 0);
INSERT INTO `sys_user_notice` VALUES (8, 8, 2, 1, NULL, now(), now(), 0);
INSERT INTO `sys_user_notice` VALUES (9, 9, 2, 1, NULL, now(), now(), 0);
INSERT INTO `sys_user_notice` VALUES (10, 10, 2, 1, NULL, now(), now(), 0);
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,21 @@
package com.youlai.boot;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableScheduling;
/**
* 应用启动类
*
* @author Ray.Hao
* @since 0.0.1
*/
@SpringBootApplication
@EnableScheduling
public class YouLaiBootApplication {
public static void main(String[] args) {
SpringApplication.run(YouLaiBootApplication.class, args);
}
}
@@ -0,0 +1,86 @@
package com.youlai.boot.auth.controller;
import com.youlai.boot.auth.model.LoginReq;
import com.youlai.boot.common.enums.ActionTypeEnum;
import com.youlai.boot.common.enums.LogModuleEnum;
import com.youlai.boot.common.result.Result;
import com.youlai.boot.auth.service.AuthService;
import com.youlai.boot.common.annotation.Log;
import com.youlai.boot.framework.captcha.model.CaptchaInfo;
import com.youlai.boot.framework.security.model.AuthenticationToken;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;
/**
* 认证控制层
*
* @author Ray.Hao
* @since 0.0.1
*/
@Tag(name = "01.认证中心")
@RestController
@RequestMapping("/api/v1/auth")
@RequiredArgsConstructor
@Slf4j
public class AuthController {
private final AuthService authService;
@Operation(summary = "获取验证码")
@GetMapping("/captcha")
public Result<CaptchaInfo> getCaptcha() {
CaptchaInfo captcha = authService.getCaptcha();
return Result.success(captcha);
}
@Operation(summary = "账号密码登录")
@PostMapping("/login")
@Log(module = LogModuleEnum.LOGIN, value = ActionTypeEnum.LOGIN)
public Result<AuthenticationToken> login(@RequestBody @Valid LoginReq request) {
AuthenticationToken authenticationToken = authService.login(request.getUsername(), request.getPassword());
return Result.success(authenticationToken);
}
@Operation(summary = "短信验证码登录")
@PostMapping("/login/sms")
@Log(module = LogModuleEnum.LOGIN, value = ActionTypeEnum.LOGIN)
public Result<AuthenticationToken> loginBySms(
@Parameter(description = "手机号", example = "18888888888") @RequestParam String mobile,
@Parameter(description = "验证码", example = "123456") @RequestParam String code
) {
AuthenticationToken loginResult = authService.loginBySms(mobile, code);
return Result.success(loginResult);
}
@Operation(summary = "发送登录短信验证码")
@PostMapping("/sms/code")
public Result<Void> sendSmsCode(
@Parameter(description = "手机号", example = "18888888888") @RequestParam String mobile
) {
authService.sendSmsCode(mobile);
return Result.success();
}
@Operation(summary = "退出登录")
@DeleteMapping("/logout")
@Log(module = LogModuleEnum.LOGIN, value = ActionTypeEnum.LOGOUT)
public Result<Void> logout() {
authService.logout();
return Result.success();
}
@Operation(summary = "刷新令牌")
@PostMapping("/refresh-token")
public Result<AuthenticationToken> refreshToken(
@Parameter(description = "刷新令牌", example = "xxx.xxx.xxx") @RequestParam String refreshToken
) {
AuthenticationToken authenticationToken = authService.refreshToken(refreshToken);
return Result.success(authenticationToken);
}
}
@@ -0,0 +1,89 @@
package com.youlai.boot.auth.controller;
import com.youlai.boot.auth.model.WxMaBindMobileReq;
import com.youlai.boot.auth.model.WxMaPhoneLoginReq;
import com.youlai.boot.auth.model.WxMaLoginResp;
import com.youlai.boot.auth.service.WxMaAuthService;
import com.youlai.boot.common.annotation.Log;
import com.youlai.boot.common.enums.ActionTypeEnum;
import com.youlai.boot.common.enums.LogModuleEnum;
import com.youlai.boot.common.result.Result;
import com.youlai.boot.framework.security.model.AuthenticationToken;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import jakarta.validation.Valid;
/**
* 微信小程序认证控制层
*
* @author Ray.Hao
* @since 2.4.0
*/
@Tag(name = "13.微信小程序认证")
@RestController
@RequestMapping("/api/v1/wxma/auth")
@RequiredArgsConstructor
@Slf4j
public class WxMaAuthController {
private final WxMaAuthService wxMaAuthService;
/**
* 静默登录
* <p>
* 适用场景:个人小程序、无需手机号的登录场景
* <ul>
* <li>已绑定手机号的用户:直接返回 token,登录成功</li>
* <li>未绑定手机号的用户:返回 openid,需调用绑定手机号接口</li>
* </ul>
*/
@Operation(summary = "静默登录", description = "通过微信 code 登录,已绑定用户直接返回 token,未绑定用户返回 openid 需绑定手机号")
@PostMapping("/silent-login")
@Log(module = LogModuleEnum.LOGIN, value = ActionTypeEnum.LOGIN)
public Result<WxMaLoginResp> silentLogin(
@Parameter(description = "微信登录凭证(wx.login 获取)", required = true, example = "0xxx")
@RequestParam String code
) {
WxMaLoginResp result = wxMaAuthService.silentLogin(code);
return Result.success(result);
}
/**
* 手机号快捷登录
* <p>
* 适用场景:企业认证小程序(已开通手机号快捷登录权限)
* <p>
* 一步完成登录,无需绑定流程,自动创建新用户
*/
@Operation(summary = "手机号快捷登录", description = "同时使用微信 code 和手机号授权 code 登录,适用于企业认证小程序")
@PostMapping("/phone-login")
@Log(module = LogModuleEnum.LOGIN, value = ActionTypeEnum.LOGIN)
public Result<AuthenticationToken> phoneLogin(@Valid @RequestBody WxMaPhoneLoginReq req) {
AuthenticationToken result = wxMaAuthService.phoneLogin(req.getLoginCode(), req.getPhoneCode());
return Result.success(result);
}
/**
* 绑定手机号
* <p>
* 适用场景:静默登录后未绑定手机号的用户
* <p>
* 绑定成功后自动完成登录
*/
@Operation(summary = "绑定手机号", description = "为静默登录用户绑定手机号,绑定成功后自动登录")
@PostMapping("/bind-mobile")
@Log(module = LogModuleEnum.LOGIN, value = ActionTypeEnum.LOGIN)
public Result<AuthenticationToken> bindMobile(@Valid @RequestBody WxMaBindMobileReq req) {
AuthenticationToken result = wxMaAuthService.bindMobile(req.getOpenid(), req.getMobile(), req.getSmsCode());
return Result.success(result);
}
}
@@ -0,0 +1,31 @@
package com.youlai.boot.auth.model;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import jakarta.validation.constraints.NotBlank;
/**
* 登录请求参数
*
* @author Ray.Hao
* @since 3.0.0
*/
@Schema(description = "登录请求参数")
@Data
public class LoginReq {
@Schema(description = "用户名", requiredMode = Schema.RequiredMode.REQUIRED, example = "admin")
@NotBlank(message = "用户名不能为空")
private String username;
@Schema(description = "密码", requiredMode = Schema.RequiredMode.REQUIRED, example = "123456")
@NotBlank(message = "密码不能为空")
private String password;
@Schema(description = "验证码缓存ID", example = "captcha_id_123")
private String captchaId;
@Schema(description = "验证码", example = "123456")
private String captchaCode;
}
@@ -0,0 +1,28 @@
package com.youlai.boot.auth.model;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import lombok.Data;
/**
* 微信小程序绑定手机号请求
*
* @author Ray.Hao
* @since 3.0.0
*/
@Schema(description = "微信小程序绑定手机号请求")
@Data
public class WxMaBindMobileReq {
@NotBlank(message = "openid 不能为空")
@Schema(description = "微信用户唯一标识(静默登录返回)", example = "oVBkZ0aYgDMDIywRdgPW8-joxXc4")
private String openid;
@NotBlank(message = "手机号不能为空")
@Schema(description = "手机号码", example = "18888888888")
private String mobile;
@NotBlank(message = "短信验证码不能为空")
@Schema(description = "短信验证码", example = "123456")
private String smsCode;
}
@@ -0,0 +1,42 @@
package com.youlai.boot.auth.model;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 微信小程序登录响应
*
* @author Ray.Hao
* @since 2.4.0
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Schema(description = "微信小程序登录响应")
public class WxMaLoginResp {
@Schema(description = "是否新用户")
private Boolean isNewUser;
@Schema(description = "是否需要绑定手机号")
private Boolean needBindMobile;
@Schema(description = "微信openid(绑定手机号时需要)")
private String openid;
@Schema(description = "访问令牌")
private String accessToken;
@Schema(description = "刷新令牌")
private String refreshToken;
@Schema(description = "令牌类型")
private String tokenType;
@Schema(description = "过期时间(秒)")
private Integer expiresIn;
}
@@ -0,0 +1,24 @@
package com.youlai.boot.auth.model;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import lombok.Data;
/**
* 微信小程序手机号快捷登录请求
*
* @author Ray.Hao
* @since 3.0.0
*/
@Schema(description = "微信小程序手机号快捷登录请求")
@Data
public class WxMaPhoneLoginReq {
@NotBlank(message = "微信登录凭证不能为空")
@Schema(description = "微信登录凭证(wx.login 获取)", example = "0xxx")
private String loginCode;
@NotBlank(message = "手机号授权凭证不能为空")
@Schema(description = "手机号授权凭证(getPhoneNumber 事件获取)", example = "0xxx")
private String phoneCode;
}
@@ -0,0 +1,56 @@
package com.youlai.boot.auth.service;
import com.youlai.boot.framework.captcha.model.CaptchaInfo;
import com.youlai.boot.framework.security.model.AuthenticationToken;
/**
* 认证服务接口
*
* @author Ray.Hao
* @since 2.4.0
*/
public interface AuthService {
/**
* 账号密码登录
*
* @param username 用户名
* @param password 密码
* @return 认证令牌
*/
AuthenticationToken login(String username, String password);
/**
* 短信验证码登录
*
* @param mobile 手机号
* @param code 验证码
* @return 认证令牌
*/
AuthenticationToken loginBySms(String mobile, String code);
/**
* 发送短信验证码
*
* @param mobile 手机号
*/
void sendSmsCode(String mobile);
/**
* 退出登录
*/
void logout();
/**
* 获取验证码
*/
CaptchaInfo getCaptcha();
/**
* 刷新令牌
*
* @param refreshToken 刷新令牌
* @return 认证令牌
*/
AuthenticationToken refreshToken(String refreshToken);
}
@@ -0,0 +1,53 @@
package com.youlai.boot.auth.service;
import com.youlai.boot.auth.model.WxMaLoginResp;
import com.youlai.boot.framework.security.model.AuthenticationToken;
/**
* 微信小程序认证服务接口
*
* @author Ray.Hao
* @since 2.4.0
*/
public interface WxMaAuthService {
/**
* 静默登录
* <p>
* 通过微信登录凭证(code)获取用户唯一标识(openid),
* 如果用户已绑定手机号则直接登录成功,否则返回需绑定手机号的提示。
* </p>
*
* @param code 微信登录凭证(wx.login 获取)
* @return 登录结果(成功返回 token,需绑定返回 openid
*/
WxMaLoginResp silentLogin(String code);
/**
* 手机号快捷登录
* <p>
* 同时使用微信登录凭证和手机号授权凭证,
* 一步完成用户注册/登录,无需额外绑定流程。
* 适用于企业认证的小程序(已开通手机号快捷登录权限)。
* </p>
*
* @param loginCode 微信登录凭证(wx.login 获取)
* @param phoneCode 手机号授权凭证(getPhoneNumber 事件获取)
* @return 认证令牌
*/
AuthenticationToken phoneLogin(String loginCode, String phoneCode);
/**
* 绑定手机号
* <p>
* 为已静默登录但未绑定手机号的用户绑定手机号,
* 绑定成功后自动完成登录。
* </p>
*
* @param openid 微信用户唯一标识
* @param mobile 手机号码
* @param smsCode 短信验证码
* @return 认证令牌
*/
AuthenticationToken bindMobile(String openid, String mobile, String smsCode);
}
@@ -0,0 +1,148 @@
package com.youlai.boot.auth.service.impl;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.StrUtil;
import com.youlai.boot.auth.service.AuthService;
import com.youlai.boot.common.constant.RedisConstants;
import com.youlai.boot.framework.captcha.model.CaptchaInfo;
import com.youlai.boot.framework.captcha.service.CaptchaService;
import com.youlai.boot.framework.security.model.AuthenticationToken;
import com.youlai.boot.framework.security.model.SmsAuthenticationToken;
import com.youlai.boot.framework.security.token.TokenManager;
import com.youlai.boot.framework.security.util.SecurityUtils;
import com.youlai.boot.framework.integration.sms.enums.SmsTypeEnum;
import com.youlai.boot.framework.integration.sms.service.SmsService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Service;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
/**
* 认证服务实现类
*
* @author Ray.Hao
* @since 2.4.0
*/
@Service
@RequiredArgsConstructor
@Slf4j
public class AuthServiceImpl implements AuthService {
private final AuthenticationManager authenticationManager;
private final TokenManager tokenManager;
private final SmsService smsService;
private final RedisTemplate<String, Object> redisTemplate;
private final CaptchaService captchaService;
/**
* 用户名密码登录
*
* @param username 用户名
* @param password 密码
* @return 访问令牌
*/
@Override
public AuthenticationToken login(String username, String password) {
// 1. 创建用于密码认证的令牌(未认证)
UsernamePasswordAuthenticationToken authenticationToken =
new UsernamePasswordAuthenticationToken(username.trim(), password);
// 2. 执行认证(认证中)
// 说明:这里的认证流程由 Spring Security 提供的 AuthenticationManager 执行。
// 默认情况下会委托给 DaoAuthenticationProvider
// 1) retrieveUser(...):内部通过 UserDetailsService.loadUserByUsername(...) 获取用户信息(本项目为 SysUserDetailsService 实现)
// 2) additionalAuthenticationChecks(...):对比请求密码与用户存储密码(由 PasswordEncoder 完成匹配)
// 认证通过后返回已认证的 Authenticationprincipal 为 SysUserDetailsauthorities 为角色/权限集合)。
Authentication authentication = authenticationManager.authenticate(authenticationToken);
// 3. 认证成功后生成 JWT 令牌,并存入 Security 上下文,供登录日志 AOP 使用(已认证)
AuthenticationToken authenticationTokenResponse =
tokenManager.generateToken(authentication);
SecurityContextHolder.getContext().setAuthentication(authentication);
return authenticationTokenResponse;
}
/**
* 发送登录短信验证码
*
* @param mobile 手机号
*/
@Override
public void sendSmsCode(String mobile) {
String code = RandomUtil.randomNumbers(6);
// 发送短信验证码
Map<String, String> templateParams = new HashMap<>();
templateParams.put("code", code);
try {
smsService.sendSms(mobile, SmsTypeEnum.LOGIN, templateParams);
} catch (Exception e) {
log.error("发送短信验证码失败", e);
}
// 缓存验证码至Redis,用于登录校验
redisTemplate.opsForValue().set(StrUtil.format(RedisConstants.Captcha.SMS_LOGIN_CODE, mobile), code, 5, TimeUnit.MINUTES);
}
/**
* 短信验证码登录
*
* @param mobile 手机号
* @param code 验证码
* @return 访问令牌
*/
@Override
public AuthenticationToken loginBySms(String mobile, String code) {
// 1. 创建用户短信验证码认证的令牌(未认证)
SmsAuthenticationToken smsAuthenticationToken = new SmsAuthenticationToken(mobile, code);
// 2. 执行认证(认证中)
Authentication authentication = authenticationManager.authenticate(smsAuthenticationToken);
// 3. 认证成功后生成 JWT 令牌,并存入 Security 上下文,供登录日志 AOP 使用(已认证)
AuthenticationToken authenticationToken = tokenManager.generateToken(authentication);
SecurityContextHolder.getContext().setAuthentication(authentication);
return authenticationToken;
}
/**
* 注销登录
*/
@Override
public void logout() {
String token = SecurityUtils.getAccessToken();
if (StrUtil.isNotBlank(token)) {
tokenManager.invalidateToken(token);
// 清除Security上下文
SecurityContextHolder.clearContext();
}
}
/**
* 获取验证码
*/
@Override
public CaptchaInfo getCaptcha() {
return captchaService.generate();
}
/**
* 刷新token
*
* @param refreshToken 刷新令牌
* @return 新的访问令牌
*/
@Override
public AuthenticationToken refreshToken(String refreshToken) {
return tokenManager.refreshToken(refreshToken);
}
}
@@ -0,0 +1,245 @@
package com.youlai.boot.auth.service.impl;
import cn.binarywang.wx.miniapp.api.WxMaService;
import cn.binarywang.wx.miniapp.bean.WxMaJscode2SessionResult;
import cn.binarywang.wx.miniapp.bean.WxMaPhoneNumberInfo;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import com.youlai.boot.auth.model.WxMaLoginResp;
import com.youlai.boot.auth.service.WxMaAuthService;
import com.youlai.boot.common.constant.RedisConstants;
import com.youlai.boot.framework.security.exception.NeedBindMobileException;
import com.youlai.boot.framework.security.model.AuthenticationToken;
import com.youlai.boot.framework.security.model.SysUserDetails;
import com.youlai.boot.framework.security.model.WxMaAuthenticationToken;
import com.youlai.boot.framework.security.token.TokenManager;
import com.youlai.boot.system.enums.SocialPlatformEnum;
import com.youlai.boot.system.model.entity.SysUser;
import com.youlai.boot.system.service.UserSocialService;
import com.youlai.boot.system.service.UserService;
import com.youlai.boot.system.service.UserRoleService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.time.LocalDateTime;
import java.util.Collections;
/**
* 微信小程序认证服务实现
*
* @author Ray.Hao
* @since 4.0.0
*/
@Service
@RequiredArgsConstructor
@Slf4j
public class WxMaAuthServiceImpl implements WxMaAuthService {
private final WxMaService wxMaService;
private final AuthenticationManager authenticationManager;
private final TokenManager tokenManager;
private final UserService userService;
private final UserSocialService userSocialService;
private final UserRoleService userRoleService;
private final RedisTemplate<String, Object> redisTemplate;
/**
* 静默登录
*/
@Override
public WxMaLoginResp silentLogin(String code) {
WxMaAuthenticationToken token = new WxMaAuthenticationToken(code);
try {
Authentication authentication = authenticationManager.authenticate(token);
AuthenticationToken authToken = tokenManager.generateToken(authentication);
SecurityContextHolder.getContext().setAuthentication(authentication);
return WxMaLoginResp.builder()
.isNewUser(false)
.needBindMobile(false)
.accessToken(authToken.getAccessToken())
.refreshToken(authToken.getRefreshToken())
.tokenType(authToken.getTokenType())
.expiresIn(authToken.getExpiresIn())
.build();
} catch (NeedBindMobileException e) {
return WxMaLoginResp.builder()
.isNewUser(true)
.needBindMobile(true)
.openid(e.getOpenid())
.build();
}
}
/**
* 手机号快捷登录
*/
@Override
@Transactional(rollbackFor = Exception.class)
public AuthenticationToken phoneLogin(String loginCode, String phoneCode) {
// 1. 解析微信登录凭证,获取会话信息
WxMaJscode2SessionResult session = resolveSession(loginCode);
String openid = session.getOpenid();
// 2. 解析手机号授权凭证,获取手机号
String mobile = resolvePhoneNumber(phoneCode);
log.info("微信小程序手机号快捷登录:openid={}, mobile={}", openid, mobile);
// 3. 查询或创建用户
SysUser user = findOrCreateUser(mobile);
// 4. 绑定微信 openid
bindWechatOpenid(user, session);
// 5. 生成认证令牌
return generateAuthToken(mobile);
}
/**
* 绑定手机号
*/
@Override
@Transactional(rollbackFor = Exception.class)
public AuthenticationToken bindMobile(String openid, String mobile, String smsCode) {
// 1. 验证短信验证码
validateSmsCode(mobile, smsCode);
// 2. 查询或创建用户
SysUser user = findOrCreateUser(mobile);
// 3. 绑定微信 openid
userSocialService.bindOrUpdate(
user.getId(),
SocialPlatformEnum.WECHAT_MINI,
openid,
null, null, null, null
);
log.info("微信小程序绑定手机号成功:mobile={}, openid={}", mobile, openid);
// 4. 生成认证令牌
return generateAuthToken(mobile);
}
// ==================== 私有方法 ====================
/**
* 解析微信登录凭证,获取会话信息
*/
private WxMaJscode2SessionResult resolveSession(String loginCode) {
try {
return wxMaService.jsCode2SessionInfo(loginCode);
} catch (Exception e) {
log.error("获取微信会话信息失败,loginCode={}", loginCode, e);
throw new IllegalArgumentException("微信登录失败:" + e.getMessage());
}
}
/**
* 解析手机号授权凭证,获取手机号
*/
private String resolvePhoneNumber(String phoneCode) {
try {
WxMaPhoneNumberInfo phoneInfo = wxMaService.getUserService().getPhoneNoInfo(phoneCode);
return phoneInfo.getPhoneNumber();
} catch (Exception e) {
log.error("获取微信手机号失败,phoneCode={}", phoneCode, e);
throw new IllegalArgumentException("获取手机号失败:" + e.getMessage());
}
}
/**
* 查询或创建用户
*/
private SysUser findOrCreateUser(String mobile) {
SysUser user = userService.lambdaQuery()
.eq(SysUser::getMobile, mobile)
.one();
if (user == null) {
user = createNewUser(mobile);
log.info("微信小程序登录:创建新用户,mobile={}, userId={}", mobile, user.getId());
}
return user;
}
/**
* 创建新用户
* <p>
* 新用户默认分配 GUEST(访问游客)角色
* </p>
*/
private SysUser createNewUser(String mobile) {
SysUser user = new SysUser();
user.setMobile(mobile);
user.setUsername("wx_" + IdUtil.fastSimpleUUID().substring(0, 8));
user.setNickname("微信用户");
user.setStatus(1);
user.setIsDeleted(0);
user.setCreateTime(LocalDateTime.now());
user.setUpdateTime(LocalDateTime.now());
userService.save(user);
// 分配 GUEST 角色(角色ID=3
userRoleService.saveUserRoles(user.getId(), Collections.singletonList(3L));
return user;
}
/**
* 绑定微信 openid
*/
private void bindWechatOpenid(SysUser user, WxMaJscode2SessionResult session) {
try {
userSocialService.bindOrUpdate(
user.getId(),
SocialPlatformEnum.WECHAT_MINI,
session.getOpenid(),
session.getUnionid(),
user.getNickname(),
user.getAvatar(),
session.getSessionKey()
);
} catch (Exception e) {
// 绑定失败不影响登录
log.warn("绑定微信 openid 失败,userId={}, openid={}", user.getId(), session.getOpenid(), e);
}
}
/**
* 验证短信验证码
*/
private void validateSmsCode(String mobile, String smsCode) {
String cacheKey = StrUtil.format(RedisConstants.Captcha.SMS_LOGIN_CODE, mobile);
String cachedCode = (String) redisTemplate.opsForValue().get(cacheKey);
if (!StrUtil.equals(smsCode, cachedCode)) {
throw new IllegalArgumentException("验证码错误");
}
// 验证成功后删除验证码
redisTemplate.delete(cacheKey);
}
/**
* 生成认证令牌
*/
private AuthenticationToken generateAuthToken(String mobile) {
SysUserDetails userDetails = new SysUserDetails(userService.getAuthInfoByMobile(mobile));
Authentication authentication = new UsernamePasswordAuthenticationToken(
userDetails, null, userDetails.getAuthorities()
);
AuthenticationToken authToken = tokenManager.generateToken(authentication);
SecurityContextHolder.getContext().setAuthentication(authentication);
return authToken;
}
}
@@ -0,0 +1,98 @@
package com.youlai.boot.codegen.config;
import cn.hutool.core.io.file.FileNameUtil;
import cn.hutool.core.map.MapUtil;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.Map;
/**
* 代码生成配置属性
*
* @author Ray
* @since 2.11.0
*/
@Component
@EnableConfigurationProperties(CodegenProperties.class)
@ConfigurationProperties(prefix = "codegen")
@Data
public class CodegenProperties {
/**
* 默认配置
*/
private DefaultConfig defaultConfig ;
/**
* 模板配置
*/
private Map<String, TemplateConfig> templateConfigs = MapUtil.newHashMap(true);
/**
* 后端应用名
*/
private String backendAppName;
/**
* 前端应用名
*/
private String frontendAppName;
/**
* 下载文件名
*/
private String downloadFileName;
/**
* 排除数据表
*/
private List<String> excludeTables;
/**
* 模板配置
*/
@Data
public static class TemplateConfig {
/**
* 模板路径 (e.g. /templates/codegen/controller.java.vm)
*/
private String templatePath;
/**
* 子包名 (e.g. controller/service/mapper/model)
*/
private String subpackageName;
/**
* 文件扩展名,如 .java
*/
private String extension = FileNameUtil.EXT_JAVA;
}
/**
* 默认配置
*/
@Data
public static class DefaultConfig {
/**
* 作者 (e.g. Ray)
*/
private String author;
/**
* 默认模块名(e.g. system)
*/
private String moduleName;
}
}
@@ -0,0 +1,112 @@
package com.youlai.boot.codegen.controller;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.youlai.boot.common.result.PageResult;
import com.youlai.boot.common.result.Result;
import com.youlai.boot.codegen.config.CodegenProperties;
import com.youlai.boot.common.enums.ActionTypeEnum;
import com.youlai.boot.common.enums.LogModuleEnum;
import com.youlai.boot.codegen.service.CodegenService;
import com.youlai.boot.codegen.model.form.GenConfigForm;
import com.youlai.boot.codegen.model.query.TableQuery;
import com.youlai.boot.codegen.model.vo.CodegenPreviewVO;
import com.youlai.boot.codegen.model.vo.TablePageVO;
import com.youlai.boot.common.annotation.Log;
import com.youlai.boot.codegen.service.GenTableService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.ServletOutputStream;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;
import java.io.IOException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.List;
/**
* 代码生成器控制层
*
* @author Ray
* @since 2.10.0
*/
@Tag(name = "11.代码生成")
@RestController
@RequestMapping("/api/v1/codegen")
@RequiredArgsConstructor
@Slf4j
public class CodegenController {
private final CodegenService codegenService;
private final GenTableService genTableService;
private final CodegenProperties codegenProperties;
@Operation(summary = "获取数据表分页列表")
@GetMapping("/table")
public PageResult<TablePageVO> getTablePage(
TableQuery queryParams
) {
Page<TablePageVO> result = codegenService.getTablePage(queryParams);
return PageResult.success(result);
}
@Operation(summary = "获取代码生成配置")
@GetMapping("/{tableName}/config")
public Result<GenConfigForm> getGenTableFormData(
@Parameter(description = "表名", example = "sys_user") @PathVariable String tableName
) {
GenConfigForm formData = genTableService.getGenTableFormData(tableName);
return Result.success(formData);
}
@Operation(summary = "保存代码生成配置")
@PostMapping("/{tableName}/config")
@Log(module = LogModuleEnum.CODEGEN, value = ActionTypeEnum.UPDATE)
public Result<?> saveGenConfig(@RequestBody GenConfigForm formData) {
genTableService.saveGenConfig(formData);
return Result.success();
}
@Operation(summary = "删除代码生成配置")
@DeleteMapping("/{tableName}/config")
public Result<?> deleteGenConfig(
@Parameter(description = "表名", example = "sys_user") @PathVariable String tableName
) {
genTableService.deleteGenConfig(tableName);
return Result.success();
}
@Operation(summary = "获取预览生成代码")
@GetMapping("/{tableName}/preview")
public Result<List<CodegenPreviewVO>> getTablePreviewData(@PathVariable String tableName,
@RequestParam(value = "pageType", required = false, defaultValue = "classic") String pageType,
@RequestParam(value = "type", required = false, defaultValue = "ts") String type) {
List<CodegenPreviewVO> list = codegenService.getCodegenPreviewData(tableName, pageType, type);
return Result.success(list);
}
@Operation(summary = "下载代码")
@GetMapping("/{tableName}/download")
@Log(module = LogModuleEnum.CODEGEN, value = ActionTypeEnum.DOWNLOAD)
public void downloadZip(HttpServletResponse response, @PathVariable String tableName,
@RequestParam(value = "pageType", required = false, defaultValue = "classic") String pageType,
@RequestParam(value = "type", required = false, defaultValue = "ts") String type) {
String[] tableNames = tableName.split(",");
byte[] data = codegenService.downloadCode(tableNames, pageType, type);
response.reset();
response.setHeader("Content-Disposition", "attachment; filename=" + URLEncoder.encode(codegenProperties.getDownloadFileName(), StandardCharsets.UTF_8));
response.setContentType("application/octet-stream; charset=UTF-8");
try (ServletOutputStream outputStream = response.getOutputStream()) {
outputStream.write(data);
outputStream.flush();
} catch (IOException e) {
log.error("Error while writing the zip file1 to response", e);
throw new RuntimeException("Failed to write the zip file1 to response", e);
}
}
}
@@ -0,0 +1,41 @@
package com.youlai.boot.codegen.converter;
import com.youlai.boot.codegen.model.entity.GenTable;
import com.youlai.boot.codegen.model.entity.GenTableColumn;
import com.youlai.boot.codegen.model.form.GenConfigForm;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import java.util.List;
/**
* 代码生成配置转换器
*
* @author Ray
* @since 2.10.0
*/
@Mapper(componentModel = "spring")
public interface CodegenConverter {
@Mapping(source = "genTable.tableName", target = "tableName")
@Mapping(source = "genTable.businessName", target = "businessName")
@Mapping(source = "genTable.moduleName", target = "moduleName")
@Mapping(source = "genTable.packageName", target = "packageName")
@Mapping(source = "genTable.entityName", target = "entityName")
@Mapping(source = "genTable.author", target = "author")
@Mapping(source = "genTable.pageType", target = "pageType")
@Mapping(source = "genTable.removeTablePrefix", target = "removeTablePrefix")
@Mapping(source = "fieldConfigs", target = "fieldConfigs")
GenConfigForm toGenConfigForm(GenTable genTable, List<GenTableColumn> fieldConfigs);
List<GenConfigForm.FieldConfig> toGenTableColumnForm(List<GenTableColumn> fieldConfigs);
GenConfigForm.FieldConfig toGenTableColumnForm(GenTableColumn genTableColumn);
GenTable toGenTable(GenConfigForm formData);
List<GenTableColumn> toGenTableColumn(List<GenConfigForm.FieldConfig> fieldConfigs);
GenTableColumn toGenTableColumn(GenConfigForm.FieldConfig fieldConfig);
}
@@ -0,0 +1,89 @@
package com.youlai.boot.codegen.enums;
import com.baomidou.mybatisplus.annotation.EnumValue;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonValue;
import com.youlai.boot.common.base.IBaseEnum;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
/**
* 表单类型枚举
*
* @author Ray
* @since 2.10.0
*/
@Getter
@RequiredArgsConstructor
public enum FormTypeEnum implements IBaseEnum<Integer> {
/**
* 输入框
*/
INPUT(1, "输入框"),
/**
* 下拉框
*/
SELECT(2, "下拉框"),
/**
* 单选框
*/
RADIO(3, "单选框"),
/**
* 复选框
*/
CHECK_BOX(4, "复选框"),
/**
* 数字输入框
*/
INPUT_NUMBER(5, "数字输入框"),
/**
* 开关
*/
SWITCH(6, "开关"),
/**
* 文本域
*/
TEXT_AREA(7, "文本域"),
/**
* 日期时间框
*/
DATE(8, "日期框"),
/**
* 日期框
*/
DATE_TIME(9, "日期时间框"),
/**
* 隐藏域
*/
HIDDEN(10, "隐藏域");
// Mybatis-Plus 提供注解表示插入数据库时插入该值
@EnumValue
@JsonValue
private final Integer value;
// @JsonValue // 表示对枚举序列化时返回此字段
private final String label;
@JsonCreator
public static FormTypeEnum fromValue(Integer value) {
for (FormTypeEnum type : FormTypeEnum.values()) {
if (type.getValue().equals(value)) {
return type;
}
}
throw new IllegalArgumentException("No enum constant with value " + value);
}
}
@@ -0,0 +1,108 @@
package com.youlai.boot.codegen.enums;
import lombok.Getter;
import java.util.HashMap;
import java.util.Map;
/**
* 表单类型枚举
*
* @author Ray
* @since 2.10.0
*/
@Getter
public enum JavaTypeEnum {
VARCHAR("varchar", "String", "string"),
CHAR("char", "String", "string"),
BLOB("blob", "byte[]", "Uint8Array"),
TEXT("text", "String", "string"),
JSON("json", "String", "any"),
INTEGER("int", "Integer", "number"),
TINYINT("tinyint", "Integer", "number"),
SMALLINT("smallint", "Integer", "number"),
MEDIUMINT("mediumint", "Integer", "number"),
BIGINT("bigint", "Long", "number"),
FLOAT("float", "Float", "number"),
DOUBLE("double", "Double", "number"),
DECIMAL("decimal", "BigDecimal", "number"),
DATE("date", "LocalDate", "string"),
DATETIME("datetime", "LocalDateTime", "string"),
TIMESTAMP("timestamp", "LocalDateTime", "string"),
BOOLEAN("boolean", "Boolean", "boolean"),
BIT("bit", "Boolean", "boolean");
// 数据库类型
private final String dbType;
// Java类型
private final String javaType;
// TypeScript类型
private final String tsType;
// 数据库类型和Java类型的映射
private static final Map<String, JavaTypeEnum> typeMap = new HashMap<>();
// 初始化映射关系
static {
for (JavaTypeEnum javaTypeEnum : JavaTypeEnum.values()) {
typeMap.put(javaTypeEnum.getDbType(), javaTypeEnum);
}
}
JavaTypeEnum(String dbType, String javaType, String tsType) {
this.dbType = dbType;
this.javaType = javaType;
this.tsType = tsType;
}
/**
* 根据数据库类型获取对应的Java类型
*
* @param columnType 列类型
* @return 对应的Java类型
*/
public static String getJavaTypeByColumnType(String columnType) {
String normalized = normalizeColumnType(columnType);
JavaTypeEnum javaTypeEnum = typeMap.get(normalized);
if (javaTypeEnum != null) {
return javaTypeEnum.getJavaType();
}
return "String";
}
/**
* 根据Java类型获取对应的TypeScript类型
*
* @param javaType Java类型
* @return 对应的TypeScript类型
*/
public static String getTsTypeByJavaType(String javaType) {
if (javaType == null) {
return "any";
}
for (JavaTypeEnum javaTypeEnum : JavaTypeEnum.values()) {
if (javaTypeEnum.getJavaType().equals(javaType)) {
return javaTypeEnum.getTsType();
}
}
return "any";
}
private static String normalizeColumnType(String columnType) {
if (columnType == null) {
return "";
}
// Handle values like: varchar(255), bigint unsigned, INT
String normalized = columnType.trim().toLowerCase();
int parenIndex = normalized.indexOf('(');
if (parenIndex > -1) {
normalized = normalized.substring(0, parenIndex);
}
// Remove modifiers
normalized = normalized.replace("unsigned", "").replace("zerofill", "").trim();
// Collapse repeated spaces
normalized = normalized.replaceAll("\\s+", " ");
return normalized;
}
}
@@ -0,0 +1,73 @@
package com.youlai.boot.codegen.enums;
import com.baomidou.mybatisplus.annotation.EnumValue;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonValue;
import com.youlai.boot.common.base.IBaseEnum;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
/**
* 查询类型枚举
*
* @author Ray
* @since 2.10.0
*/
@Getter
@RequiredArgsConstructor
public enum QueryTypeEnum implements IBaseEnum<Integer> {
/** 等于 */
EQ(1, "="),
/** 模糊匹配 */
LIKE(2, "LIKE '%s%'"),
/** 包含 */
IN(3, "IN"),
/** 范围 */
BETWEEN(4, "BETWEEN"),
/** 大于 */
GT(5, ">"),
/** 大于等于 */
GE(6, ">="),
/** 小于 */
LT(7, "<"),
/** 小于等于 */
LE(8, "<="),
/** 不等于 */
NE(9, "!="),
/** 左模糊匹配 */
LIKE_LEFT(10, "LIKE '%s'"),
/** 右模糊匹配 */
LIKE_RIGHT(11, "LIKE 's%'");
// 存储在数据库中的枚举属性值
@EnumValue
@JsonValue
private final Integer value;
// 序列化成 JSON 时的属性值
private final String label;
@JsonCreator
public static QueryTypeEnum fromValue(Integer value) {
for (QueryTypeEnum type : QueryTypeEnum.values()) {
if (type.getValue().equals(value)) {
return type;
}
}
throw new IllegalArgumentException("No enum constant with value " + value);
}
}
@@ -0,0 +1,41 @@
package com.youlai.boot.codegen.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.youlai.boot.codegen.model.vo.ColumnMetaVO;
import com.youlai.boot.codegen.model.vo.TableMetaVO;
import com.youlai.boot.codegen.model.query.TableQuery;
import com.youlai.boot.codegen.model.vo.TablePageVO;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
/**
* 数据库映射层
*
* @author Ray
* @since 2.9.0
*/
@Mapper
public interface DatabaseMapper extends BaseMapper {
/**
* 获取表分页列表
*
* @param page
* @param queryParams
* @return
*/
Page<TablePageVO> getTablePage(Page<TablePageVO> page, TableQuery queryParams);
/**
* 获取表字段列表
*
* @param tableName
* @return
*/
List<ColumnMetaVO> getTableColumns(String tableName);
TableMetaVO getTableMetadata(String tableName);
}
@@ -0,0 +1,20 @@
package com.youlai.boot.codegen.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.youlai.boot.codegen.model.entity.GenTableColumn;
import org.apache.ibatis.annotations.Mapper;
/**
* 代码生成表字段配置访问层
*
* @author Ray
* @since 2.10.0
*/
@Mapper
public interface GenTableColumnMapper extends BaseMapper<GenTableColumn> {
}
@@ -0,0 +1,20 @@
package com.youlai.boot.codegen.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.youlai.boot.codegen.model.entity.GenTable;
import org.apache.ibatis.annotations.Mapper;
/**
* 代码生成表配置访问层
*
* @author Ray
* @since 2.10.0
*/
@Mapper
public interface GenTableMapper extends BaseMapper<GenTable> {
}
@@ -0,0 +1,65 @@
package com.youlai.boot.codegen.model.entity;
import com.baomidou.mybatisplus.annotation.*;
import com.youlai.boot.common.base.BaseEntity;
import lombok.Getter;
import lombok.Setter;
/**
* 代码生成表配置
*
* @author Ray
* @since 2.10.0
*/
@TableName(value = "gen_table")
@Getter
@Setter
public class GenTable extends BaseEntity {
/**
* 表名
*/
private String tableName;
/**
* 包名
*/
private String packageName;
/**
* 模块名
*/
private String moduleName;
/**
* 实体类名
*/
private String entityName;
/**
* 业务名
*/
private String businessName;
/**
* 父菜单ID
*/
private Long parentMenuId;
/**
* 作者
*/
private String author;
/**
* 页面类型 classic|curd
*/
private String pageType;
/**
* 要移除的表前缀,如: sys_
*/
private String removeTablePrefix;
}
@@ -0,0 +1,107 @@
package com.youlai.boot.codegen.model.entity;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.youlai.boot.common.base.BaseEntity;
import com.youlai.boot.codegen.enums.FormTypeEnum;
import com.youlai.boot.codegen.enums.QueryTypeEnum;
import lombok.Getter;
import lombok.Setter;
/**
* 代码生成表字段配置实体
*
* @author Ray
* @since 2.10.0
*/
@TableName(value = "gen_table_column")
@Getter
@Setter
public class GenTableColumn extends BaseEntity {
/**
* 关联的表配置ID
*/
private Long tableId;
/**
* 列名
*/
private String columnName;
/**
* 列类型
*/
private String columnType;
/**
* 字段长度
*/
private Long maxLength;
/**
* 字段名称
*/
private String fieldName;
/**
* 字段排序
*/
private Integer fieldSort;
/**
* 字段类型
*/
private String fieldType;
/**
* 字段描述
*/
private String fieldComment;
/**
* 表单类型
*/
private FormTypeEnum formType;
/**
* 查询方式
*/
private QueryTypeEnum queryType;
/**
* 是否在列表显示
*/
private Integer isShowInList;
/**
* 是否在表单显示
*/
private Integer isShowInForm;
/**
* 是否在查询条件显示
*/
private Integer isShowInQuery;
/**
* 是否必填
*/
private Integer isRequired;
/**
* TypeScript类型
*/
@TableField(exist = false)
@JsonIgnore
private String tsType;
/**
* 字典类型
*/
private String dictType;
}
@@ -0,0 +1,109 @@
package com.youlai.boot.codegen.model.form;
import com.youlai.boot.codegen.enums.FormTypeEnum;
import com.youlai.boot.codegen.enums.QueryTypeEnum;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.util.List;
/**
* 代码生成配置表单
*
* @author Ray
* @since 2.10.0
*/
@Schema(description = "代码生成配置表单")
@Data
public class GenConfigForm {
@Schema(description = "主键",example = "1")
private Long id;
@Schema(description = "表名",example = "sys_user")
private String tableName;
@Schema(description = "业务名",example = "用户")
private String businessName;
@Schema(description = "模块名",example = "system")
private String moduleName;
@Schema(description = "包名",example = "com.youlai")
private String packageName;
@Schema(description = "实体名",example = "User")
private String entityName;
@Schema(description = "作者",example = "youlaitech")
private String author;
@Schema(description = "上级菜单ID",example = "1")
private Long parentMenuId;
@Schema(description = "字段配置列表")
private List<FieldConfig> fieldConfigs;
@Schema(description = "后端应用名")
private String backendAppName;
@Schema(description = "前端应用名")
private String frontendAppName;
@Schema(description = "页面类型 classic|curd", example = "classic")
private String pageType;
@Schema(description = "要移除的表前缀,如: sys_", example = "sys_")
private String removeTablePrefix;
@Schema(description = "字段配置")
@Data
public static class FieldConfig {
@Schema(description = "主键")
private Long id;
@Schema(description = "列名")
private String columnName;
@Schema(description = "列类型")
private String columnType;
@Schema(description = "字段名")
private String fieldName;
@Schema(description = "字段排序")
private Integer fieldSort;
@Schema(description = "字段类型")
private String fieldType;
@Schema(description = "字段描述")
private String fieldComment;
@Schema(description = "是否在列表显示")
private Integer isShowInList;
@Schema(description = "是否在表单显示")
private Integer isShowInForm;
@Schema(description = "是否在查询条件显示")
private Integer isShowInQuery;
@Schema(description = "是否必填")
private Integer isRequired;
@Schema(description = "最大长度")
private Integer maxLength;
@Schema(description = "表单类型")
private FormTypeEnum formType;
@Schema(description = "查询类型")
private QueryTypeEnum queryType;
@Schema(description = "字典类型")
private String dictType;
}
}
@@ -0,0 +1,31 @@
package com.youlai.boot.codegen.model.query;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.youlai.boot.common.base.BaseQuery;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Getter;
import lombok.Setter;
import java.util.List;
/**
* 数据表分页查询对象
*
* @author Ray
* @since 2.10.0
*/
@Schema(description = "数据表分页查询对象")
@Getter
@Setter
public class TablePageQuery extends BaseQuery {
@Schema(description="关键字(表名)")
private String keywords;
/**
* 排除的表名
*/
@JsonIgnore
private List<String> excludeTables;
}
@@ -0,0 +1,31 @@
package com.youlai.boot.codegen.model.query;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.youlai.boot.common.base.BaseQuery;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Getter;
import lombok.Setter;
import java.util.List;
/**
* 数据表查询对象
*
* @author Ray
* @since 2.10.0
*/
@Schema(description = "数据表查询对象")
@Getter
@Setter
public class TableQuery extends BaseQuery {
@Schema(description="关键字(表名)")
private String keywords;
/**
* 排除的表名
*/
@JsonIgnore
private List<String> excludeTables;
}
@@ -0,0 +1,25 @@
package com.youlai.boot.codegen.model.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@Schema(description = "代码生成代码预览Vo")
@Data
public class CodegenPreviewVO {
@Schema(description = "生成文件路径")
private String path;
@Schema(description = "生成文件名称",example = "SysUser.java" )
private String fileName;
@Schema(description = "生成文件内容")
private String content;
@Schema(description = "文件范围(frontend/backend)")
private String scope;
@Schema(description = "文件语言(扩展名)")
private String language;
}
@@ -0,0 +1,26 @@
package com.youlai.boot.codegen.model.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@Schema(description = "数据表字段元数据")
@Data
public class ColumnMetaVO {
private String columnName;
private String dataType;
private String columnComment;
private Long characterMaximumLength;
private Integer isPrimaryKey;
private String isNullable;
private String characterSetName;
private String collationName;
}
@@ -0,0 +1,26 @@
package com.youlai.boot.codegen.model.vo;
import lombok.Data;
/**
* 数据表元数据
*
* @author Ray
* @since 2.10.0
*/
@Data
public class TableMetaVO {
private String tableName;
private String tableComment;
private String tableCollation;
private String engine;
private String charset;
private String createTime;
}
@@ -0,0 +1,32 @@
package com.youlai.boot.codegen.model.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@Schema(description = "表视图对象")
@Data
public class TablePageVO {
@Schema(description = "表名称", example = "sys_user")
private String tableName;
@Schema(description = "表描述",example = "用户表")
private String tableComment;
@Schema(description = "表排序规则",example = "utf8mb4_general_ci")
private String tableCollation;
@Schema(description = "存储引擎",example = "InnoDB")
private String engine;
@Schema(description = "字符集",example = "utf8mb4")
private String charset;
@Schema(description = "创建时间",example = "2023-08-08 08:08:08")
private String createTime;
@Schema(description="是否已配置")
private Integer isConfigured;
}
@@ -0,0 +1,40 @@
package com.youlai.boot.codegen.service;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.youlai.boot.codegen.model.query.TableQuery;
import com.youlai.boot.codegen.model.vo.CodegenPreviewVO;
import com.youlai.boot.codegen.model.vo.TablePageVO;
import java.util.List;
/**
* 代码生成配置接口
*
* @author Ray
* @since 2.10.0
*/
public interface CodegenService {
/**
* 获取数据表分页列表
*
* @param queryParams 查询参数
* @return
*/
Page<TablePageVO> getTablePage(TableQuery queryParams);
/**
* 获取预览生成代码
*
* @param tableName 表名
* @return
*/
List<CodegenPreviewVO> getCodegenPreviewData(String tableName, String pageType, String type);
/**
* 下载代码
* @param tableNames 表名
* @return
*/
byte[] downloadCode(String[] tableNames, String pageType, String type);
}
@@ -0,0 +1,14 @@
package com.youlai.boot.codegen.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.youlai.boot.codegen.model.entity.GenTableColumn;
/**
* 代码生成配置接口
*
* @author Ray
* @since 2.10.0
*/
public interface GenTableColumnService extends IService<GenTableColumn> {
}
@@ -0,0 +1,39 @@
package com.youlai.boot.codegen.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.youlai.boot.codegen.model.entity.GenTable;
import com.youlai.boot.codegen.model.form.GenConfigForm;
/**
* 代码生成配置接口
*
* @author Ray
* @since 2.10.0
*/
public interface GenTableService extends IService<GenTable> {
/**
* 获取代码生成配置
*
* @param tableName 表名
* @return
*/
GenConfigForm getGenTableFormData(String tableName);
/**
* 保存代码生成配置
*
* @param formData 表单数据
* @return
*/
void saveGenConfig(GenConfigForm formData);
/**
* 删除代码生成配置
*
* @param tableName 表名
* @return
*/
void deleteGenConfig(String tableName);
}
@@ -0,0 +1,428 @@
package com.youlai.boot.codegen.service.impl;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.io.file.FileNameUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.template.Template;
import cn.hutool.extra.template.TemplateConfig;
import cn.hutool.extra.template.TemplateEngine;
import cn.hutool.extra.template.TemplateUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.youlai.boot.codegen.enums.JavaTypeEnum;
import com.youlai.boot.codegen.config.CodegenProperties;
import com.youlai.boot.codegen.service.GenTableService;
import com.youlai.boot.codegen.service.GenTableColumnService;
import com.youlai.boot.codegen.service.CodegenService;
import com.youlai.boot.common.exception.BusinessException;
import com.youlai.boot.codegen.mapper.DatabaseMapper;
import com.youlai.boot.codegen.model.entity.GenTable;
import com.youlai.boot.codegen.model.entity.GenTableColumn;
import com.youlai.boot.codegen.model.query.TableQuery;
import com.youlai.boot.codegen.model.vo.CodegenPreviewVO;
import com.youlai.boot.codegen.model.vo.TablePageVO;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
/**
* 代码生成服务实现类。
*
* <p>
* 根据代码生成配置({@link CodegenProperties})与表/字段元数据,渲染模板并提供预览与下载能力。
* </p>
*
* @author Ray
* @since 2.10.0
*/
@Service
@RequiredArgsConstructor
@Slf4j
public class CodegenServiceImpl implements CodegenService {
private final DatabaseMapper databaseMapper;
private final CodegenProperties codegenProperties;
private final GenTableService genTableService;
private final GenTableColumnService genTableColumnService;
/**
* 数据表分页列表
*
* @param queryParams 查询参数
* @return 分页结果
*/
public Page<TablePageVO> getTablePage(TableQuery queryParams) {
Page<TablePageVO> page = new Page<>(queryParams.getPageNum(), queryParams.getPageSize());
// 设置排除的表
List<String> excludeTables = codegenProperties.getExcludeTables();
queryParams.setExcludeTables(excludeTables);
return databaseMapper.getTablePage(page, queryParams);
}
/**
* 解析前端模板路径
*
* @param templateName 模板标识
* @param templateConfig 模板配置
* @param frontendType 前端类型
* @return 模板路径
*/
private String resolveFrontendTemplatePath(String templateName,
CodegenProperties.TemplateConfig templateConfig,
String frontendType) {
if (!"js".equals(frontendType)) {
return templateConfig.getTemplatePath();
}
if ("API".equals(templateName)) {
return "codegen/frontend/js/api.js.vm";
}
if ("VIEW".equals(templateName)) {
return "codegen/frontend/js/index.js.vue.vm";
}
return templateConfig.getTemplatePath();
}
/**
* 解析前端文件后缀
*
* @param templateName 模板标识
* @param templateConfig 模板配置
* @param frontendType 前端类型
* @return 文件后缀
*/
private String resolveFrontendExtension(String templateName,
CodegenProperties.TemplateConfig templateConfig,
String frontendType) {
if (!"js".equals(frontendType)) {
return templateConfig.getExtension();
}
if ("API".equals(templateName) || "API_TYPES".equals(templateName)) {
return ".js";
}
return templateConfig.getExtension();
}
/**
* 获取预览生成代码
*
* @param tableName 表名
* @return 预览数据
*/
@Override
public List<CodegenPreviewVO> getCodegenPreviewData(String tableName, String pageType, String type) {
List<CodegenPreviewVO> list = new ArrayList<>();
GenTable genTable = genTableService.getOne(new LambdaQueryWrapper<GenTable>()
.eq(GenTable::getTableName, tableName)
);
if (genTable == null) {
throw new BusinessException("未找到表生成配置");
}
List<GenTableColumn> fieldConfigs = genTableColumnService.list(new LambdaQueryWrapper<GenTableColumn>()
.eq(GenTableColumn::getTableId, genTable.getId())
.orderByAsc(GenTableColumn::getFieldSort)
);
if (CollectionUtil.isEmpty(fieldConfigs)) {
throw new BusinessException("未找到字段生成配置");
}
// 遍历模板配置
Map<String, CodegenProperties.TemplateConfig> templateConfigs = codegenProperties.getTemplateConfigs();
String frontendType = StrUtil.blankToDefault(type, "ts").toLowerCase();
for (Map.Entry<String, CodegenProperties.TemplateConfig> templateConfigEntry : templateConfigs.entrySet()) {
CodegenPreviewVO previewVo = new CodegenPreviewVO();
CodegenProperties.TemplateConfig templateConfig = templateConfigEntry.getValue();
String templateName = templateConfigEntry.getKey();
if ("js".equals(frontendType) && "API_TYPES".equals(templateName)) {
continue;
}
String effectiveTemplatePath = resolveFrontendTemplatePath(templateName, templateConfig, frontendType);
String extension = resolveFrontendExtension(templateName, templateConfig, frontendType);
/* 1. 生成文件名 UserController */
// User Role Menu Dept
String entityName = genTable.getEntityName();
// Controller Service Mapper Entity
// .java .ts .vue
// 文件名 UserController.java
String fileName = getFileName(entityName, templateName, extension);
previewVo.setFileName(fileName);
previewVo.setScope(resolveScope(templateName));
previewVo.setLanguage(resolveLanguage(fileName));
/* 2. 生成文件路径 */
// 包名:com.youlai.boot
String packageName = genTable.getPackageName();
// 模块名:system
String moduleName = genTable.getModuleName();
// 子包名:controller
String subpackageName = templateConfig.getSubpackageName();
// 组合成文件路径:src/main/java/com/youlai/boot/system/controller
String filePath = getFilePath(templateName, moduleName, packageName, subpackageName, entityName);
previewVo.setPath(filePath);
/* 3. 生成文件内容 */
// 将模板文件中的变量替换为具体的值 生成代码内容
// 优先使用保存的 ui,没有则使用请求参数
String finalType = StrUtil.blankToDefault(genTable.getPageType(), pageType);
String content = getCodeContent(
effectiveTemplatePath,
templateConfig.getSubpackageName(),
genTable,
fieldConfigs,
finalType
);
previewVo.setContent(content);
list.add(previewVo);
}
return list;
}
private String resolveScope(String templateName) {
return switch (templateName) {
case "API", "API_TYPES", "VIEW" -> "frontend";
default -> "backend";
};
}
private String resolveLanguage(String fileName) {
return FileNameUtil.extName(fileName).toLowerCase();
}
/**
* 生成文件名。
*
* <p>部分模板需要使用约定的命名规则(例如前端 API 文件)。</p>
*
* @param entityName 实体名(例如 User
* @param templateName 模板名(例如 Entity、Controller、API
* @param extension 文件后缀(例如 .java、.ts)
* @return 文件名
*/
private String getFileName(String entityName, String templateName, String extension) {
if ("Entity".equals(templateName)) {
return entityName + extension;
} else if ("MapperXml".equals(templateName)) {
return entityName + "Mapper" + extension;
} else if ("API".equals(templateName)) {
return "index" + extension;
} else if ("API_TYPES".equals(templateName)) {
return "types" + extension;
} else if ("VIEW".equals(templateName)) {
return "index.vue";
}
return entityName + templateName + extension;
}
/**
* 生成文件路径。
*
* @param templateName 模板名
* @param moduleName 模块名(例如 system
* @param packageName 包名(例如 com.youlai.boot
* @param subPackageName 子包名(例如 controller、service.impl、api、views
* @param entityName 实体名(例如 User
* @return 生成文件路径
*/
private String getFilePath(String templateName, String moduleName, String packageName, String subPackageName, String entityName) {
String path;
if ("MapperXml".equals(templateName)) {
path = (codegenProperties.getBackendAppName()
+ File.separator
+ "src" + File.separator + "main" + File.separator + "resources"
+ File.separator + subPackageName
+ File.separator + moduleName
);
} else if ("API".equals(templateName)) {
path = (codegenProperties.getFrontendAppName()
+ File.separator + "src"
+ File.separator + "api"
+ File.separator + moduleName
+ File.separator + StrUtil.toSymbolCase(entityName, '-')
);
} else if ("API_TYPES".equals(templateName)) {
path = (codegenProperties.getFrontendAppName()
+ File.separator + "src"
+ File.separator + "api"
+ File.separator + moduleName
+ File.separator + StrUtil.toSymbolCase(entityName, '-')
);
} else if ("VIEW".equals(templateName)) {
// path = "src/views/system/user";
path = (codegenProperties.getFrontendAppName()
+ File.separator + "src"
+ File.separator + subPackageName
+ File.separator + moduleName
+ File.separator + StrUtil.toSymbolCase(entityName, '-')
);
} else {
path = (codegenProperties.getBackendAppName()
+ File.separator
+ "src" + File.separator + "main" + File.separator + "java"
+ File.separator + packageName
+ File.separator + moduleName
+ File.separator + subPackageName
);
}
// subPackageName = model.entity => model/entity
path = path.replace(".", File.separator);
return path;
}
/**
* 渲染模板,生成代码内容。
*
* @param templateConfig 模板配置
* @param genTable 表生成配置
* @param fieldConfigs 字段配置
* @param pageType 前端页面类型
* @return 渲染后的代码内容
*/
private String getCodeContent(String templatePath,
String subpackageName,
GenTable genTable,
List<GenTableColumn> fieldConfigs,
String pageType) {
Map<String, Object> bindMap = new HashMap<>();
String entityName = genTable.getEntityName();
bindMap.put("packageName", genTable.getPackageName());
bindMap.put("moduleName", genTable.getModuleName());
bindMap.put("subpackageName", subpackageName);
bindMap.put("date", DateUtil.format(new Date(), "yyyy-MM-dd HH:mm"));
bindMap.put("entityName", entityName);
bindMap.put("tableName", genTable.getTableName());
bindMap.put("author", genTable.getAuthor());
String entityLowerCamel = StrUtil.lowerFirst(entityName);
String entityKebab = StrUtil.toSymbolCase(entityName, '-');
String entityUpperSnake = StrUtil.toSymbolCase(entityName, '_').toUpperCase();
bindMap.put("entityLowerCamel", entityLowerCamel);
bindMap.put("entityKebab", entityKebab);
bindMap.put("entityUpperSnake", entityUpperSnake);
bindMap.put("businessName", genTable.getBusinessName());
bindMap.put("entityComment", genTable.getBusinessName());
bindMap.put("fieldConfigs", fieldConfigs);
boolean hasLocalDateTime = false;
boolean hasBigDecimal = false;
boolean hasRequiredField = false;
for (GenTableColumn fieldConfig : fieldConfigs) {
if (StrUtil.isBlank(fieldConfig.getFieldType())) {
fieldConfig.setFieldType(JavaTypeEnum.getJavaTypeByColumnType(fieldConfig.getColumnType()));
}
if ("LocalDateTime".equals(fieldConfig.getFieldType()) || "LocalDate".equals(fieldConfig.getFieldType())) {
hasLocalDateTime = true;
}
if ("BigDecimal".equals(fieldConfig.getFieldType())) {
hasBigDecimal = true;
}
if (ObjectUtil.equals(fieldConfig.getIsRequired(), 1)) {
hasRequiredField = true;
}
fieldConfig.setTsType(JavaTypeEnum.getTsTypeByJavaType(fieldConfig.getFieldType()));
}
bindMap.put("hasLocalDateTime", hasLocalDateTime);
bindMap.put("hasBigDecimal", hasBigDecimal);
bindMap.put("hasRequiredField", hasRequiredField);
TemplateEngine templateEngine = TemplateUtil.createEngine(new TemplateConfig("templates", TemplateConfig.ResourceMode.CLASSPATH));
// 根据 ui 选择不同的前端页面模板:默认 index.vue.vm;封装版使用 index.curd.vue.vm
String path = templatePath;
if ("curd".equalsIgnoreCase(pageType)) {
if (path.endsWith("index.js.vue.vm")) {
path = path.replace("index.js.vue.vm", "index.curd.js.vue.vm");
} else if (path.endsWith("index.vue.vm")) {
path = path.replace("index.vue.vm", "index.curd.vue.vm");
}
}
Template template = templateEngine.getTemplate(path);
return template.render(bindMap);
}
/**
* 下载代码。
*
* @param tableNames 表名数组,支持多张表
* @param ui 页面类型
* @return zip 压缩文件字节数组
*/
@Override
public byte[] downloadCode(String[] tableNames, String ui, String type) {
try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
ZipOutputStream zip = new ZipOutputStream(outputStream)) {
// 遍历每个表名,生成对应的代码并压缩到 zip 文件中
for (String tableName : tableNames) {
generateAndZipCode(tableName, zip, ui, type);
}
// 确保所有压缩数据写入输出流,避免数据残留在内存缓冲区引发的数据不完整
zip.finish();
return outputStream.toByteArray();
} catch (IOException e) {
log.error("Error while generating zip for code download", e);
throw new RuntimeException("Failed to generate code zip file1", e);
}
}
/**
* 根据表名生成代码并压缩到 zip 文件中。
*
* @param tableName 表名
* @param zip 压缩文件输出流
* @param ui 页面类型
*/
private void generateAndZipCode(String tableName, ZipOutputStream zip, String ui, String type) {
List<CodegenPreviewVO> codePreviewList = getCodegenPreviewData(tableName, ui, type);
for (CodegenPreviewVO codePreview : codePreviewList) {
String fileName = codePreview.getFileName();
String content = codePreview.getContent();
String path = codePreview.getPath();
try {
// 创建压缩条目
ZipEntry zipEntry = new ZipEntry(path + File.separator + fileName);
zip.putNextEntry(zipEntry);
// 写入文件内容
zip.write(content.getBytes(StandardCharsets.UTF_8));
// 关闭当前压缩条目
zip.closeEntry();
} catch (IOException e) {
log.error("Error while adding file1 {} to zip", fileName, e);
}
}
}
}
@@ -0,0 +1,21 @@
package com.youlai.boot.codegen.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.youlai.boot.codegen.mapper.GenTableColumnMapper;
import com.youlai.boot.codegen.model.entity.GenTableColumn;
import com.youlai.boot.codegen.service.GenTableColumnService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
/**
* 代码生成字段配置服务实现类
*
* @author Ray.Hao
* @since 2.10.0
*/
@Service
@RequiredArgsConstructor
public class GenTableColumnServiceImpl extends ServiceImpl<GenTableColumnMapper, GenTableColumn> implements GenTableColumnService {
}
@@ -0,0 +1,227 @@
package com.youlai.boot.codegen.service.impl;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.youlai.boot.YouLaiBootApplication;
import com.youlai.boot.common.enums.EnvEnum;
import com.youlai.boot.codegen.enums.FormTypeEnum;
import com.youlai.boot.codegen.enums.JavaTypeEnum;
import com.youlai.boot.codegen.enums.QueryTypeEnum;
import com.youlai.boot.common.exception.BusinessException;
import com.youlai.boot.codegen.config.CodegenProperties;
import com.youlai.boot.codegen.converter.CodegenConverter;
import com.youlai.boot.codegen.mapper.DatabaseMapper;
import com.youlai.boot.codegen.mapper.GenTableMapper;
import com.youlai.boot.codegen.model.vo.ColumnMetaVO;
import com.youlai.boot.codegen.model.vo.TableMetaVO;
import com.youlai.boot.codegen.model.entity.GenTable;
import com.youlai.boot.codegen.model.entity.GenTableColumn;
import com.youlai.boot.codegen.model.form.GenConfigForm;
import com.youlai.boot.codegen.service.GenTableService;
import com.youlai.boot.codegen.service.GenTableColumnService;
import com.youlai.boot.system.service.MenuService;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Objects;
/**
* 数据库服务实现类
*
* @author Ray
* @since 2.10.0
*/
@Service
@RequiredArgsConstructor
public class GenTableServiceImpl extends ServiceImpl<GenTableMapper, GenTable> implements GenTableService {
private final DatabaseMapper databaseMapper;
private final CodegenProperties codegenProperties;
private final GenTableColumnService genTableColumnService;
private final CodegenConverter codegenConverter;
@Value("${spring.profiles.active}")
private String springProfilesActive;
private final MenuService menuService;
/**
* 获取代码生成配置
*
* @param tableName 表名 eg: sys_user
* @return 代码生成配置
*/
@Override
public GenConfigForm getGenTableFormData(String tableName) {
// 查询表生成配置
GenTable genTable = this.getOne(
new LambdaQueryWrapper<>(GenTable.class)
.eq(GenTable::getTableName, tableName)
.last("LIMIT 1")
);
// 是否有代码生成配置
boolean hasGenTable = genTable != null;
// 如果没有代码生成配置,则根据表的元数据生成默认配置
if (genTable == null) {
TableMetaVO tableMetadata = databaseMapper.getTableMetadata(tableName);
Assert.isTrue(tableMetadata != null, "未找到表元数据");
genTable = new GenTable();
genTable.setTableName(tableName);
// 表注释作为业务名称,去掉表字 例如:用户表 -> 用户
String tableComment = tableMetadata.getTableComment();
if (StrUtil.isNotBlank(tableComment)) {
genTable.setBusinessName(tableComment.replace("", "").trim());
}
// 根据表名生成实体类名,支持去除前缀 例如:sys_user -> SysUser
String removePrefix = genTable.getRemoveTablePrefix();
String processedTable = tableName;
if (StrUtil.isNotBlank(removePrefix) && StrUtil.startWith(tableName, removePrefix)) {
processedTable = StrUtil.removePrefix(tableName, removePrefix);
}
genTable.setEntityName(StrUtil.toCamelCase(StrUtil.upperFirst(StrUtil.toCamelCase(processedTable))));
genTable.setPackageName(YouLaiBootApplication.class.getPackageName());
genTable.setModuleName(codegenProperties.getDefaultConfig().getModuleName()); // 默认模块名
genTable.setAuthor(codegenProperties.getDefaultConfig().getAuthor());
}
// 根据表的列 + 已经存在的字段生成配置 得到 组合后的字段生成配置
List<GenTableColumn> genTableColumns = new ArrayList<>();
// 获取表的列
List<ColumnMetaVO> tableColumns = databaseMapper.getTableColumns(tableName);
if (CollectionUtil.isNotEmpty(tableColumns)) {
// 查询字段生成配置
List<GenTableColumn> fieldConfigList = genTableColumnService.list(
new LambdaQueryWrapper<GenTableColumn>()
.eq(GenTableColumn::getTableId, genTable.getId())
.orderByAsc(GenTableColumn::getFieldSort)
);
Integer maxSort = fieldConfigList.stream()
.map(GenTableColumn::getFieldSort)
.filter(Objects::nonNull) // 过滤掉空值
.max(Integer::compareTo)
.orElse(0);
for (ColumnMetaVO tableColumn : tableColumns) {
// 根据列名获取字段生成配置
String columnName = tableColumn.getColumnName();
GenTableColumn fieldConfig = fieldConfigList.stream()
.filter(item -> StrUtil.equals(item.getColumnName(), columnName))
.findFirst()
.orElseGet(() -> createDefaultFieldConfig(tableColumn));
if (fieldConfig.getFieldSort() == null) {
fieldConfig.setFieldSort(++maxSort);
}
// 根据列类型设置字段类型
String fieldType = fieldConfig.getFieldType();
if (StrUtil.isBlank(fieldType)) {
String javaType = JavaTypeEnum.getJavaTypeByColumnType(fieldConfig.getColumnType());
fieldConfig.setFieldType(javaType);
}
// 如果没有代码生成配置,则默认展示在列表和表单
if (!hasGenTable) {
fieldConfig.setIsShowInList(1);
fieldConfig.setIsShowInForm(1);
}
genTableColumns.add(fieldConfig);
}
}
// 对 genTableColumns 按照 fieldSort 排序
genTableColumns = genTableColumns.stream().sorted(Comparator.comparing(GenTableColumn::getFieldSort)).toList();
GenConfigForm genConfigForm = codegenConverter.toGenConfigForm(genTable, genTableColumns);
genConfigForm.setFrontendAppName(codegenProperties.getFrontendAppName());
genConfigForm.setBackendAppName(codegenProperties.getBackendAppName());
return genConfigForm;
}
/**
* 创建默认字段配置
*
* @param columnMetaVO 表字段元数据
* @return
*/
private GenTableColumn createDefaultFieldConfig(ColumnMetaVO columnMetaVO) {
GenTableColumn fieldConfig = new GenTableColumn();
fieldConfig.setColumnName(columnMetaVO.getColumnName());
fieldConfig.setColumnType(columnMetaVO.getDataType());
fieldConfig.setFieldComment(columnMetaVO.getColumnComment());
fieldConfig.setFieldName(StrUtil.toCamelCase(columnMetaVO.getColumnName()));
fieldConfig.setIsRequired("YES".equals(columnMetaVO.getIsNullable()) ? 0 : 1);
String columnType = StrUtil.blankToDefault(fieldConfig.getColumnType(), "").toLowerCase();
if ("date".equals(columnType)) {
fieldConfig.setFormType(FormTypeEnum.DATE);
} else if ("datetime".equals(columnType) || "timestamp".equals(columnType)) {
fieldConfig.setFormType(FormTypeEnum.DATE_TIME);
} else {
fieldConfig.setFormType(FormTypeEnum.INPUT);
}
fieldConfig.setQueryType(QueryTypeEnum.EQ);
fieldConfig.setMaxLength(columnMetaVO.getCharacterMaximumLength());
return fieldConfig;
}
/**
* 保存代码生成配置
*
* @param formData 代码生成配置表单
*/
@Override
public void saveGenConfig(GenConfigForm formData) {
GenTable genTable = codegenConverter.toGenTable(formData);
this.saveOrUpdate(genTable);
// 如果选择上级菜单且当前环境不是生产环境,则保存菜单
Long parentMenuId = formData.getParentMenuId();
if (parentMenuId != null && !EnvEnum.PROD.getValue().equals(springProfilesActive)) {
menuService.addMenuForCodegen(parentMenuId, genTable);
}
List<GenTableColumn> genTableColumns = codegenConverter.toGenTableColumn(formData.getFieldConfigs());
if (CollectionUtil.isEmpty(genTableColumns)) {
throw new BusinessException("字段配置不能为空");
}
genTableColumns.forEach(genTableColumn -> {
genTableColumn.setTableId(genTable.getId());
});
genTableColumnService.saveOrUpdateBatch(genTableColumns);
}
/**
* 删除代码生成配置
*
* @param tableName 表名
*/
@Override
public void deleteGenConfig(String tableName) {
GenTable genTable = this.getOne(new LambdaQueryWrapper<GenTable>()
.eq(GenTable::getTableName, tableName));
boolean result = this.remove(new LambdaQueryWrapper<GenTable>()
.eq(GenTable::getTableName, tableName)
);
if (result) {
genTableColumnService.remove(new LambdaQueryWrapper<GenTableColumn>()
.eq(GenTableColumn::getTableId, genTable.getId())
);
}
}
}
@@ -0,0 +1,28 @@
package com.youlai.boot.common.annotation;
import java.lang.annotation.*;
/**
* 数据权限注解
*
* @author zc
* @since 2.0.0
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface DataPermission {
/**
* 数据权限 {@link com.baomidou.mybatisplus.extension.plugins.inner.DataPermissionInterceptor}
*/
String deptAlias() default "";
String deptIdColumnName() default "dept_id";
String userAlias() default "";
String userIdColumnName() default "create_by";
}
@@ -0,0 +1,47 @@
package com.youlai.boot.common.annotation;
import com.youlai.boot.common.enums.ActionTypeEnum;
import com.youlai.boot.common.enums.LogModuleEnum;
import java.lang.annotation.*;
/**
* 日志注解
*
* @author Ray
* @since 2024/6/25
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Documented
public @interface Log {
/**
* 模块
*
* @return 模块
*/
LogModuleEnum module();
/**
* 操作类型
*
* @return 操作类型
*/
ActionTypeEnum value();
/**
* 操作标题(可选,默认使用枚举描述)
*
* @return 标题
*/
String title() default "";
/**
* 自定义日志内容(可选,用于记录操作细节)
*
* @return 日志内容
*/
String content() default "";
}
@@ -0,0 +1,27 @@
package com.youlai.boot.common.annotation;
import java.lang.annotation.*;
/**
* 防止重复提交注解
* <p>
* 该注解用于方法上,防止在指定时间内的重复提交。 默认时间为5秒。
*
* @author Ray.Hao
* @since 2.3.0
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface RepeatSubmit {
/**
* 锁过期时间(秒)
* <p>
* 默认5秒内不允许重复提交
*/
int expire() default 5;
}
@@ -0,0 +1,35 @@
package com.youlai.boot.common.annotation;
import com.youlai.boot.common.validator.FieldValidator;
import jakarta.validation.Constraint;
import jakarta.validation.Payload;
import java.lang.annotation.*;
/**
* 用于验证字段值是否合法的注解
*
* @author Ray.Hao
* @since 2.18.0
*/
@Documented
@Constraint(validatedBy = FieldValidator.class)
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface ValidField {
/**
* 验证失败时的错误信息。
*/
String message() default "非法字段";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
/**
* 允许的合法值列表。
*/
String[] allowedValues();
}
@@ -0,0 +1,144 @@
package com.youlai.boot.common.aspect;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.useragent.UserAgent;
import cn.hutool.http.useragent.UserAgentUtil;
import com.youlai.boot.common.annotation.Log;
import com.youlai.boot.common.enums.ActionTypeEnum;
import com.youlai.boot.common.enums.LogModuleEnum;
import com.youlai.boot.common.util.IPUtils;
import com.youlai.boot.framework.security.util.SecurityUtils;
import com.youlai.boot.system.model.entity.SysLog;
import com.youlai.boot.system.service.LogService;
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import java.time.LocalDateTime;
/**
* 日志切面
*
* @author Ray.Hao
* @since 2.10.0
*/
@Aspect
@Component
@RequiredArgsConstructor
@Slf4j
public class LogAspect {
private final LogService logService;
/**
* 日志注解切点
*/
@Pointcut("@annotation(logAnnotation)")
public void logPointCut(Log logAnnotation) {
}
/**
* 环绕通知:记录操作日志
*/
@Around(value = "logPointCut(logAnnotation)", argNames = "pjp,logAnnotation")
public Object around(ProceedingJoinPoint pjp, Log logAnnotation) throws Throwable {
long startTime = System.currentTimeMillis();
// 在方法执行前获取用户信息,避免 logout 等操作清除 SecurityContext 后无法获取
Long userId = SecurityUtils.getUserId();
String username = SecurityUtils.getUsername();
Object result = null;
Exception exception = null;
try {
result = pjp.proceed();
return result;
} catch (Exception e) {
exception = e;
throw e;
} finally {
long executionTime = System.currentTimeMillis() - startTime;
// fallback:登录等场景在 proceed() 前未认证,需在 proceed() 后获取
if (userId == null) {
userId = SecurityUtils.getUserId();
username = SecurityUtils.getUsername();
}
try {
saveLogAsync(logAnnotation, executionTime, exception, userId, username);
} catch (Exception ex) {
log.error("保存操作日志失败", ex);
}
}
}
/**
* 异步保存日志
*/
@Async
public void saveLogAsync(Log logAnnotation, long executionTime, Exception exception, Long userId, String username) {
try {
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
if (attributes == null) {
return;
}
HttpServletRequest request = attributes.getRequest();
// 解析 User-Agent
String userAgentStr = request.getHeader("User-Agent");
UserAgent userAgent = UserAgentUtil.parse(userAgentStr);
// 解析 IP 地区
String ip = IPUtils.getIpAddr(request);
String region = IPUtils.getRegion(ip);
String province = null;
String city = null;
if (StrUtil.isNotBlank(region)) {
String[] parts = region.split("\\|");
if (parts.length >= 3) {
province = StrUtil.blankToDefault(parts[2], null);
city = StrUtil.blankToDefault(parts[3], null);
}
}
// 构建日志实体
LogModuleEnum module = logAnnotation.module();
ActionTypeEnum actionType = logAnnotation.value();
String title = StrUtil.blankToDefault(logAnnotation.title(),
module.getLabel() + "-" + actionType.getLabel());
String content = logAnnotation.content();
SysLog logEntity = new SysLog();
logEntity.setModule(module);
logEntity.setActionType(actionType);
logEntity.setTitle(title);
logEntity.setContent(content);
logEntity.setOperatorId(userId);
logEntity.setOperatorName(username);
logEntity.setRequestUri(request.getRequestURI());
logEntity.setRequestMethod(request.getMethod());
logEntity.setIp(ip);
logEntity.setProvince(province);
logEntity.setCity(city);
logEntity.setDevice(userAgent.getOs().getName());
logEntity.setOs(userAgent.getOs().getName());
logEntity.setBrowser(userAgent.getBrowser().getName());
logEntity.setStatus(exception == null ? 1 : 0);
logEntity.setErrorMsg(exception != null ? exception.getMessage() : null);
logEntity.setExecutionTime((int) executionTime);
logEntity.setCreateTime(LocalDateTime.now());
logService.save(logEntity);
} catch (Exception e) {
log.error("保存操作日志异常: {}", e.getMessage());
}
}
}
@@ -0,0 +1,102 @@
package com.youlai.boot.common.aspect;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.digest.DigestUtil;
import com.youlai.boot.common.constant.RedisConstants;
import com.youlai.boot.common.constant.SecurityConstants;
import com.youlai.boot.common.result.ResultCode;
import com.youlai.boot.common.exception.BusinessException;
import com.youlai.boot.common.annotation.RepeatSubmit;
import com.youlai.boot.common.util.IPUtils;
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.http.HttpHeaders;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import java.util.concurrent.TimeUnit;
/**
* 防重复提交切面
*
* @author Ray.Hao
* @since 2.3.0
*/
@Aspect
@Component
@RequiredArgsConstructor
@Slf4j
public class RepeatSubmitAspect {
private final RedissonClient redissonClient;
/**
* 防重复提交切点
*/
@Pointcut("@annotation(repeatSubmit)")
public void repeatSubmitPointCut(RepeatSubmit repeatSubmit) {
}
/**
* 环绕通知:处理防重复提交逻辑
*/
@Around(value = "repeatSubmitPointCut(repeatSubmit)", argNames = "pjp,repeatSubmit")
public Object handleRepeatSubmit(ProceedingJoinPoint pjp, RepeatSubmit repeatSubmit) throws Throwable {
String lockKey = buildLockKey();
int expire = repeatSubmit.expire();
RLock lock = redissonClient.getLock(lockKey);
boolean locked = lock.tryLock(0, expire, TimeUnit.SECONDS);
if (!locked) {
throw new BusinessException(ResultCode.DUPLICATE_SUBMISSION);
}
return pjp.proceed();
}
/**
* 生成防重复提交锁的 key
* @return 锁的 key
*/
private String buildLockKey() {
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
// 用户唯一标识
String userIdentifier = getUserIdentifier(request);
// 请求唯一标识 = 请求方法 + 请求路径 + 请求参数(严谨的做法)
String requestIdentifier = StrUtil.join(":", request.getMethod(), request.getRequestURI());
return StrUtil.format(RedisConstants.Lock.RESUBMIT, userIdentifier, requestIdentifier);
}
/**
* 获取用户唯一标识
* 1. 从请求头中获取 Token,使用 SHA-256 加密 Token 作为用户唯一标识
* 2. 如果 Token 为空,使用 IP 作为用户唯一标识
*
* @param request 请求对象
* @return 用户唯一标识
*/
private String getUserIdentifier(HttpServletRequest request) {
// 用户身份唯一标识
String userIdentifier;
// 从请求头中获取 Token
String tokenHeader = request.getHeader(HttpHeaders.AUTHORIZATION);
if (StrUtil.isNotBlank(tokenHeader) && tokenHeader.startsWith(SecurityConstants.BEARER_TOKEN_PREFIX)) {
String rawToken = tokenHeader.substring(SecurityConstants.BEARER_TOKEN_PREFIX.length()); // 去掉 Bearer 后的 Token
userIdentifier = DigestUtil.sha256Hex(rawToken); // 使用 SHA-256 加密 Token 作为用户唯一标识
} else {
userIdentifier = IPUtils.getIpAddr(request); // 使用 IP 作为用户唯一标识
}
return userIdentifier;
}
}
@@ -0,0 +1,48 @@
package com.youlai.boot.common.base;
import com.baomidou.mybatisplus.annotation.*;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonInclude;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
* 基础实体类
*
* <p>实体类的基类,包含了实体类的公共属性,如创建时间、更新时间、逻辑删除标识等</p>
*
* @author Ray
* @since 2024/6/23
*/
@Data
public class BaseEntity implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 主键ID
*/
@TableId(type = IdType.AUTO)
private Long id;
/**
* 创建时间
*/
@TableField(fill = FieldFill.INSERT)
@JsonInclude(value = JsonInclude.Include.NON_NULL)
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime createTime;
/**
* 更新时间
*/
@TableField(fill = FieldFill.INSERT_UPDATE)
@JsonInclude(value = JsonInclude.Include.NON_NULL)
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime updateTime;
}
@@ -0,0 +1,33 @@
package com.youlai.boot.common.base;
import com.youlai.boot.common.annotation.ValidField;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
@Data
@Schema
public class BaseQuery implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
@Schema(description = "页码", requiredMode = Schema.RequiredMode.NOT_REQUIRED, example = "1")
private Integer pageNum = 1;
@Schema(description = "每页记录数", requiredMode = Schema.RequiredMode.NOT_REQUIRED, example = "10")
private Integer pageSize = 10;
@Schema(description = "排序字段", requiredMode = Schema.RequiredMode.NOT_REQUIRED)
@ValidField(allowedValues = {"create_time", "update_time"})
private String sortBy;
@Schema(description = "排序方式(正序:ASC;反序:DESC)", requiredMode = Schema.RequiredMode.NOT_REQUIRED)
private String order;
public boolean isPaged() {
return pageNum != null && pageSize != null && pageSize > 0;
}
}
@@ -0,0 +1,88 @@
package com.youlai.boot.common.base;
import cn.hutool.core.util.ObjectUtil;
import java.util.EnumSet;
import java.util.Objects;
/**
* 枚举通用接口
*
* @author haoxr
* @since 2022/3/27 12:06
*/
public interface IBaseEnum<T> {
T getValue();
String getLabel();
/**
* 根据值获取枚举
*
* @param value
* @param clazz
* @param <E> 枚举
* @return
*/
static <E extends Enum<E> & IBaseEnum> E getEnumByValue(Object value, Class<E> clazz) {
Objects.requireNonNull(value);
EnumSet<E> allEnums = EnumSet.allOf(clazz); // 获取类型下的所有枚举
E matchEnum = allEnums.stream()
.filter(e -> ObjectUtil.equal(e.getValue(), value))
.findFirst()
.orElse(null);
return matchEnum;
}
/**
* 根据文本标签获取值
*
* @param value
* @param clazz
* @param <E>
* @return
*/
static <E extends Enum<E> & IBaseEnum> String getLabelByValue(Object value, Class<E> clazz) {
Objects.requireNonNull(value);
EnumSet<E> allEnums = EnumSet.allOf(clazz); // 获取类型下的所有枚举
E matchEnum = allEnums.stream()
.filter(e -> ObjectUtil.equal(e.getValue(), value))
.findFirst()
.orElse(null);
String label = null;
if (matchEnum != null) {
label = matchEnum.getLabel();
}
return label;
}
/**
* 根据文本标签获取值
*
* @param label
* @param clazz
* @param <E>
* @return
*/
static <E extends Enum<E> & IBaseEnum> Object getValueByLabel(String label, Class<E> clazz) {
Objects.requireNonNull(label);
EnumSet<E> allEnums = EnumSet.allOf(clazz); // 获取类型下的所有枚举
String finalLabel = label;
E matchEnum = allEnums.stream()
.filter(e -> ObjectUtil.equal(e.getLabel(), finalLabel))
.findFirst()
.orElse(null);
Object value = null;
if (matchEnum != null) {
value = matchEnum.getValue();
}
return value;
}
}
@@ -0,0 +1,53 @@
package com.youlai.boot.common.constant;
/**
* JWT Claims声明常量
* <p>
* JWT Claims 属于 Payload 的一部分,包含了一些实体(通常指的用户)的状态和额外的元数据。
*
* @author haoxr
* @since 2023/11/24
*/
public interface JwtClaimConstants {
/**
* 令牌类型
*/
String TOKEN_TYPE = "tokenType";
/**
* 用户ID
*/
String USER_ID = "userId";
/**
* 部门ID
*/
String DEPT_ID = "deptId";
/**
* 数据权限列表
* <p>
* 存储用户所有角色的数据权限范围,用于实现多角色权限合并(并集策略)
*/
String DATA_SCOPES = "dataScopes";
/**
* 权限(角色Code)集合
*/
String AUTHORITIES = "authorities";
/**
* Token 版本号
* <p>
* 用于用户级会话失效,当用户修改密码、被禁用、强制下线时递增版本号,
* 使该用户之前签发的所有 Token 失效。
*/
String TOKEN_VERSION = "tokenVersion";
/**
* 认证主体类型
*/
String SUBJECT_TYPE = "subjectType";
}
@@ -0,0 +1,80 @@
package com.youlai.boot.common.constant;
/**
* Redis 常量
*
* @author Theo
* @since 2024-7-29 11:46:08
*/
public interface RedisConstants {
/**
* 限流相关键
*/
interface RateLimiter {
String IP = "rate_limiter:ip:{}"; // IP限流(示例:rate_limiter:ip:192.168.1.1
}
/**
* 分布式锁相关键
*/
interface Lock {
String RESUBMIT = "lock:resubmit:{}:{}"; // 防重复提交(示例:lock:resubmit:userIdentifier:requestIdentifier
}
/**
* 认证模块
*/
interface Auth {
// 存储访问令牌对应的用户会话信息(accessToken -> UserSession
String ACCESS_TOKEN_USER = "auth:token:access:{}";
// 存储刷新令牌对应的用户会话信息(refreshToken -> UserSession
String REFRESH_TOKEN_USER = "auth:token:refresh:{}";
// 用户与访问令牌的映射(userId -> accessToken
String USER_ACCESS_TOKEN = "auth:user:access:{}";
// 用户与刷新令牌的映射(userId -> refreshToken
String USER_REFRESH_TOKEN = "auth:user:refresh:{}";
// 已撤销 Token 的 JTI(单端退出/会话注销):如果 jti 在撤销列表中,则 Token 立即无效
String BLACKLIST_TOKEN = "auth:token:blacklist:{}";
String REVOKED_JTI = BLACKLIST_TOKEN;
// 用户 Token 版本号(用于按用户失效历史 JWT):token.tokenVersion != redis.tokenVersion => token 无效
String USER_TOKEN_VERSION = "auth:user:token_version:{}";
}
/**
* 验证码模块
*/
interface Captcha {
String IMAGE_CODE = "captcha:image:{}"; // 图形验证码
String SMS_LOGIN_CODE = "captcha:sms_login:{}"; // 登录短信验证码
String SMS_REGISTER_CODE = "captcha:sms_register:{}";// 注册短信验证码
String EMAIL_LOGIN_CODE = "captcha:email_login:{}"; // 登录邮箱验证码
String EMAIL_REGISTER_CODE = "captcha:email_register:{}"; // 注册邮箱验证码
String MOBILE_CODE = "captcha:mobile:{}"; // 绑定、更换手机验证码
String EMAIL_CODE = "captcha:email:{}"; // 邮箱验证码
}
/**
* 系统模块
*/
interface System {
String CONFIG = "system:config"; // 系统配置
String ROLE_PERMS = "system:role:perms"; // 系统角色和权限映射
}
/**
* 开放接口模块
*/
interface OpenApi {
String ACCESS_TOKEN = "openapi:token:access:{}";
String IDEMPOTENCY_LOCK = "openapi:idempotency:lock:{}:{}";
String IDEMPOTENCY_RESULT = "openapi:idempotency:result:{}:{}";
String RATE_LIMIT_AUTH_IP = "openapi:rate-limit:auth-ip:{}";
String RATE_LIMIT_APP = "openapi:rate-limit:app:{}";
String SANDBOX_ACCOUNT = "openapi:sandbox:account:{}";
String SANDBOX_ACCOUNT_FLOWS = "openapi:sandbox:account:flows:{}";
String SANDBOX_ORDER_DETAIL = "openapi:sandbox:order:detail:{}:{}";
String SANDBOX_ORDER_LIST = "openapi:sandbox:orders:{}";
}
}
@@ -0,0 +1,25 @@
package com.youlai.boot.common.constant;
/**
* 安全模块常量
*
* @author Ray.Hao
* @since 2023/11/24
*/
public interface SecurityConstants {
/**
* 登录路径
*/
String LOGIN_PATH = "/api/v1/auth/login";
/**
* JWT Token 前缀
*/
String BEARER_TOKEN_PREFIX = "Bearer ";
/**
* 角色前缀,用于区分 authorities 角色和权限, ROLE_* 角色 、没有前缀的是权限
*/
String ROLE_PREFIX = "ROLE_";
}

Some files were not shown because too many files have changed in this diff Show More