Compare commits

...

5 Commits

92 changed files with 2542 additions and 820 deletions

4
.gitignore vendored
View File

@ -43,4 +43,6 @@ build/
*.cache *.cache
*.diff *.diff
*.patch *.patch
*.tmp *.tmp
!docker/zayac-admin/Dockerfile

View File

@ -1,62 +1,15 @@
version: '3' version: '3'
services: services:
mysql:
image: mysql:8.0.33
restart: always
container_name: mysql-master
ports:
- '3306:3306'
environment:
TZ: Asia/Shanghai
MYSQL_ROOT_PASSWORD: TMrmNY839KtZfpHb
# 初始化数据库(后续的初始化 SQL 会在这个库执行)
MYSQL_DATABASE: zayac_admin
#MYSQL_USER: 你的数据库用户名
#MYSQL_PASSWORD: 你的数据库密码
volumes:
- /docker/mysql/conf/:/etc/mysql/conf.d/
- /docker/mysql/data/:/var/lib/mysql/
command:
--default-authentication-plugin=mysql_native_password
--character-set-server=utf8mb4
--collation-server=utf8mb4_general_ci
--explicit_defaults_for_timestamp=true
--lower_case_table_names=1
redis:
image: redis:7.2.3
restart: always
container_name: redis
ports:
- '6379:6379'
environment:
TZ: Asia/Shanghai
volumes:
- /docker/redis/conf/redis.conf:/usr/local/redis/config/redis.conf
- /docker/redis/data/:/data/
- /docker/redis/logs/:/logs/
command: 'redis-server /usr/local/redis/config/redis.conf --appendonly yes --requirepass 你的 Redis 密码'
continew-admin-server: continew-admin-server:
build: ./zayac-admin build: ./zayac-admin
restart: always restart: always
container_name: continew-admin-server container_name: zayac-admin-server
ports: ports:
- '18000:18000' - '18000:18000'
environment: environment:
TZ: Asia/Shanghai TZ: Asia/Shanghai
DB_HOST: 172.17.0.1
DB_PORT: 3306
DB_USER: 你的数据库用户名
DB_PWD: 你的数据库密码
DB_NAME: continew_admin
REDIS_HOST: 172.17.0.1
REDIS_PORT: 6379
REDIS_PWD: 你的 Redis 密码
REDIS_DB: 0
volumes: volumes:
- /docker/continew-admin/config/:/app/config/ - /docker/zayac-admin/config/:/app/config/
- /docker/continew-admin/data/file/:/app/data/file/ - /docker/zayac-admin/data/file/:/app/data/file/
- /docker/continew-admin/logs/:/app/logs/ - /docker/zayac-admin/logs/:/app/logs/
- /docker/continew-admin/lib/:/app/lib/ - /docker/zayac-admin/lib/:/app/lib/
depends_on:
- redis
- mysql

View File

@ -1,14 +1,22 @@
# 使用 OpenJDK 17 作为基础镜像
FROM openjdk:17 FROM openjdk:17
MAINTAINER Charles7c charles7c@126.com # 维护者信息
MAINTAINER zayac stupidzayac@gmail.com
ARG JAR_FILE=./bin/*.jar # 复制 JAR 文件和配置文件到容器中
COPY ${JAR_FILE} /app/bin/app.jar COPY ./bin/zayac-admin.jar /app/bin/app.jar
# 设置工作目录
WORKDIR /app/bin WORKDIR /app/bin
# 暴露应用运行的端口
EXPOSE 18000
# 运行应用
ENTRYPOINT ["java", \ ENTRYPOINT ["java", \
"-jar", \ "-jar", \
"-XX:+UseZGC", \ "-XX:+UseZGC", \
"-Djava.security.egd=file:/dev/./urandom", \ "-Djava.security.egd=file:/dev/./urandom", \
"-Dspring.profiles.active=prod", \ "-Dspring.profiles.active=prod", \
"app.jar"] "/app/bin/app.jar"]

View File

@ -0,0 +1,108 @@
--- ### 代码生成器配置
generator:
# 排除数据表
excludeTables:
- DATABASECHANGELOG
- DATABASECHANGELOGLOCK
- gen_config
- gen_field_config
## 类型映射
typeMappings:
MYSQL:
Integer:
- int
- tinyint
- smallint
- mediumint
- integer
String:
- varchar
- char
- text
- mediumtext
- longtext
- tinytext
- json
LocalDate:
- date
LocalDateTime:
- datetime
- timestamp
Long:
- bigint
Float:
- float
Double:
- double
BigDecimal:
- decimal
Boolean:
- bit
## 模板配置
templateConfigs:
DO:
# 模板路径
templatePath: backend/Entity.ftl
# 包名称
packageName: model.entity
# 排除字段
excludeFields:
- id
- createUser
- createTime
- updateUser
- updateTime
Query:
templatePath: backend/Query.ftl
packageName: model.query
Req:
templatePath: backend/Req.ftl
packageName: model.req
Resp:
templatePath: backend/Resp.ftl
packageName: model.resp
excludeFields:
- id
- createUser
- createTime
DetailResp:
templatePath: backend/DetailResp.ftl
packageName: model.resp
excludeFields:
- id
- createUser
- createTime
- updateUser
- updateTime
Mapper:
templatePath: backend/Mapper.ftl
packageName: mapper
Service:
templatePath: backend/Service.ftl
packageName: service
ServiceImpl:
templatePath: backend/ServiceImpl.ftl
packageName: service.impl
Controller:
templatePath: backend/Controller.ftl
packageName: controller
api:
templatePath: frontend/api.ftl
packageName: src/apis
extension: .ts
backend: false
index:
templatePath: frontend/index.ftl
packageName: src/views
extension: .vue
backend: false
AddModal:
templatePath: frontend/AddModal.ftl
packageName: src/views
extension: .vue
backend: false
DetailDrawer:
templatePath: frontend/DetailDrawer.ftl
packageName: src/views
extension: .vue
backend: false

View File

@ -0,0 +1,284 @@
--- ### 项目配置
project:
# URL跨域配置默认放行此 URL第三方登录回调默认使用此 URL 为前缀,请注意更改为你实际的前端 URL
url: http://localhost:5173
# 是否为生产环境
production: true
--- ### 服务器配置
server:
# HTTP 端口(默认 8080
port: 8000
--- ### 数据源配置
spring.datasource:
type: com.zaxxer.hikari.HikariDataSource
## 动态数据源配置可配多主多从m1、s1...纯粹多库mysql、oracle...混合配置m1、s1、oracle...
dynamic:
# 是否启用 P6SpySQL 性能分析组件,该插件有性能损耗,不建议生产环境使用)
p6spy: false
# 设置默认的数据源或者数据源组默认master
primary: master
# 严格匹配数据源true未匹配到指定数据源时抛异常false使用默认数据源默认 false
strict: false
datasource:
# 主库配置(可配多个,构成多主)
master:
url: jdbc:mysql://${DB_HOST:45.89.233.228}:${DB_PORT:3306}/${DB_NAME:zayac_admin}?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf8&useSSL=false&allowMultiQueries=true&rewriteBatchedStatements=true&autoReconnect=true&maxReconnects=10&failOverReadOnly=false
username: ${DB_USER:zayac_admin}
password: ${DB_PWD:2hMtBRzZrDAkRynX}
driver-class-name: com.mysql.cj.jdbc.Driver
type: ${spring.datasource.type}
# 从库配置(可配多个,构成多从)
slave_1:
url: jdbc:mysql://${DB_HOST:38.6.218.29}:${DB_PORT:3306}/${DB_NAME:zayac_admin}?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf8&useSSL=false&allowMultiQueries=true&rewriteBatchedStatements=true&autoReconnect=true&maxReconnects=10&failOverReadOnly=false
username: zayac_admin
password: PxkF52eGTz48izZG
lazy: true
driver-class-name: com.mysql.cj.jdbc.Driver
type: ${spring.datasource.type}
# # PostgreSQL 库配置
# postgresql:
# url: jdbc:postgresql://${DB_HOST:127.0.0.1}:${DB_PORT:5432}/${DB_NAME:continew_admin}?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf8&useSSL=false&allowMultiQueries=true&rewriteBatchedStatements=true&autoReconnect=true&maxReconnects=10&failOverReadOnly=false
# username: ${DB_USER:root}
# password: ${DB_PWD:123456}
# driver-class-name: org.postgresql.Driver
# type: ${spring.datasource.type}
# Hikari 连接池配置完整配置请参阅https://github.com/brettwooldridge/HikariCP
hikari:
# 最大连接数量(默认 10根据实际环境调整
# 注意:当连接达到上限,并且没有空闲连接可用时,获取连接将在超时前阻塞最多 connectionTimeout 毫秒
max-pool-size: 20
# 获取连接超时时间(默认 30000 毫秒30 秒)
connection-timeout: 30000
# 空闲连接最大存活时间(默认 600000 毫秒10 分钟)
idle-timeout: 600000
# 保持连接活动的频率,以防止它被数据库或网络基础设施超时。该值必须小于 maxLifetime默认 0禁用
keepaliveTime: 30000
# 连接最大生存时间(默认 1800000 毫秒30 分钟)
max-lifetime: 1800000
## Liquibase 配置
spring.liquibase:
# 是否启用
enabled: false
# 配置文件路径
change-log: classpath:/db/changelog/db.changelog-master.yaml
--- ### 缓存配置
spring.data:
## Redis 配置(单机模式)
redis:
# 地址
host: ${REDIS_HOST:38.6.218.29}
# 端口(默认 6379
port: ${REDIS_PORT:6379}
# 密码(未设置密码时可为空或注释掉)
password: ${REDIS_PWD:jhkdjhkjdhsIUTYURTU_mWHmDY}
# 数据库索引
database: ${REDIS_DB:0}
# 连接超时时间
timeout: 10s
# 是否开启 SSL
ssl:
enabled: false
## Redisson 配置
redisson:
enabled: true
mode: SINGLE
## JetCache 配置
jetcache:
# 统计间隔(默认 0表示不统计
statIntervalMinutes: 0
## 本地/进程级/一级缓存配置
local:
default:
# 缓存类型
type: caffeine
# key 转换器的全局配置
keyConvertor: jackson
# 以毫秒为单位指定超时时间的全局配置
expireAfterWriteInMillis: 7200000
# 每个缓存实例的最大元素的全局配置,仅 local 类型的缓存需要指定
limit: 1000
## 远程/分布式/二级缓存配置
remote:
default:
# 缓存类型
type: redisson
# key 转换器的全局配置(用于将复杂的 KEY 类型转换为缓存实现可以接受的类型)
keyConvertor: jackson
# 以毫秒为单位指定超时时间的全局配置
expireAfterWriteInMillis: 7200000
# 2.7+ 支持两级缓存更新以后失效其他 JVM 中的 local cache但多个服务共用 Redis 同一个 channel 可能会造成广播风暴需要在这里指定channel。
# 你可以决定多个不同的服务是否共用同一个 channel如果没有指定则不开启。
broadcastChannel: ${spring.application.name}
# 序列化器的全局配置,仅 remote 类型的缓存需要指定
valueEncoder: java
valueDecoder: java
--- ### 验证码配置
continew-starter.captcha:
## 行为验证码
behavior:
enabled: true
cache-type: REDIS
water-mark: ${project.app-name}
## 图形验证码
graphic:
# 类型
type: SPEC
# 内容长度
length: 4
# 过期时间
expirationInMinutes: 2
## 其他验证码配置
captcha:
## 邮箱验证码配置
mail:
# 内容长度
length: 6
# 过期时间
expirationInMinutes: 5
# 限制时间
limitInSeconds: 60
# 模板路径
templatePath: mail/captcha.ftl
## 短信验证码配置
sms:
# 内容长度
length: 4
# 过期时间
expirationInMinutes: 5
# 模板 ID
templateId: 1
--- ### 日志配置
continew-starter.log:
# 是否打印日志,开启后可打印访问日志(类似于 Nginx access log
is-print: false
## 项目日志配置(配置重叠部分,优先级高于 logback-spring.xml 中的配置)
logging:
level:
top.continew: INFO
file:
path: ../logs
--- ### 跨域配置
continew-starter.web.cors:
enabled: true
# 配置允许跨域的域名
allowed-origins:
- ${project.url}
# 配置允许跨域的请求方式
allowed-methods: '*'
# 配置允许跨域的请求头
allowed-headers: '*'
# 配置允许跨域的响应头
exposed-headers: '*'
--- ### 接口文档配置
springdoc:
swagger-ui:
enabled: false
## 接口文档增强配置
knife4j:
# 开启生产环境屏蔽
production: true
--- ### 短信配置
sms:
# 从 YAML 读取配置
config-type: YAML
is-print: false
blends:
cloopen:
# 短信厂商
supplier: cloopen
base-url: https://app.cloopen.com:8883/2013-12-26
access-key-id: 你的Access Key
access-key-secret: 你的Access Key Secret
sdk-app-id: 你的应用ID
--- ### 邮件配置
spring.mail:
# 根据需要更换
host: smtp.126.com
port: 465
username: 你的邮箱
password: 你的邮箱授权码
properties:
mail:
smtp:
auth: true
socketFactory:
class: javax.net.ssl.SSLSocketFactory
port: 465
--- ### Just Auth 配置
justauth:
enabled: true
type:
GITEE:
client-id: 5d271b7f638941812aaf8bfc2e2f08f06d6235ef934e0e39537e2364eb8452c4
client-secret: 1f7d08**********5b7**********29e
redirect-uri: ${project.url}/social/callback?source=gitee
GITHUB:
client-id: 38080dad08cfbdfacca9
client-secret: 1f7d08**********5b7**********29e
redirect-uri: ${project.url}/social/callback?source=github
cache:
type: REDIS
--- ### Sa-Token 扩展配置
sa-token.extension:
# 安全配置:排除(放行)路径配置
security.excludes:
- /error
# 静态资源
- /*.html
- /*/*.html
- /*/*.css
- /*/*.js
- /webSocket/**
# 本地存储资源
- /file/**
--- ### 字段加/解密配置
continew-starter.security:
crypto:
enabled: true
# 对称加密算法密钥
password: abcdefghijklmnop
# 非对称加密算法密钥(在线生成 RSA 密钥对http://web.chacuo.net/netrsakeypair
public-key: MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAM51dgYtMyF+tTQt80sfFOpSV27a7t9uaUVeFrdGiVxscuizE7H8SMntYqfn9lp8a5GH5P1/GGehVjUD2gF/4kcCAwEAAQ==
private-key: MIIBVQIBADANBgkqhkiG9w0BAQEFAASCAT8wggE7AgEAAkEAznV2Bi0zIX61NC3zSx8U6lJXbtru325pRV4Wt0aJXGxy6LMTsfxIye1ip+f2WnxrkYfk/X8YZ6FWNQPaAX/iRwIDAQABAkEAk/VcAusrpIqA5Ac2P5Tj0VX3cOuXmyouaVcXonr7f+6y2YTjLQuAnkcfKKocQI/juIRQBFQIqqW/m1nmz1wGeQIhAO8XaA/KxzOIgU0l/4lm0A2Wne6RokJ9HLs1YpOzIUmVAiEA3Q9DQrpAlIuiT1yWAGSxA9RxcjUM/1kdVLTkv0avXWsCIE0X8woEjK7lOSwzMG6RpEx9YHdopjViOj1zPVH61KTxAiBmv/dlhqkJ4rV46fIXELZur0pj6WC3N7a4brR8a+CLLQIhAMQyerWl2cPNVtE/8tkziHKbwW3ZUiBXU24wFxedT9iV
--- ### 密码编码器配置
continew-starter.security:
password:
enabled: true
# BCryptPasswordEncoder
encoding-id: bcrypt
--- ### 文件上传配置
spring.servlet:
multipart:
enabled: true
# 单文件上传大小限制
max-file-size: 10MB
# 单次总上传文件大小限制
max-request-size: 20MB
## 头像支持格式配置
avatar:
support-suffix: jpg,jpeg,png,gif
webclient:
max-concurrent-requests: 60
spring:
rabbitmq:
host: 45.89.233.228
port: 5672
username: bot
password: xiaomi@123

View File

@ -0,0 +1,239 @@
--- ### 项目配置
project:
# 名称
name: Zayac Admin
# 应用名称
app-name: zayac-admin
# 版本
version: 3.1.0-SNAPSHOT
# 描述
description: 持续迭代优化的前后端分离中后台管理系统框架,开箱即用,持续提供舒适的开发体验。
# 基本包
base-package: com.zayac.admin
## 作者信息配置
contact:
name: zayac
email: stupidzayac@gmail.com
url: https://blog.charles7c.top/about/me
## 许可协议信息配置
license:
name: Apache-2.0
url: https://github.com/Charles7c/continew-admin/blob/dev/LICENSE
--- ### 日志配置
continew-starter.log:
# 包含信息
includes:
- DESCRIPTION
- MODULE
- REQUEST_HEADERS
- REQUEST_BODY
- IP_ADDRESS
- BROWSER
- OS
- RESPONSE_HEADERS
- RESPONSE_BODY
## 项目日志配置
logging:
config: classpath:logback-spring.xml
--- ### 链路跟踪配置
continew-starter.web:
trace:
enabled: true
header-name: traceId
## TLog 配置
tlog:
enable-invoke-time-print: false
pattern: '[$spanId][$traceId]'
mdc-enable: false
--- ### 线程池配置
continew-starter.thread-pool:
enabled: true
# 队列容量
queue-capacity: 128
# 活跃时间(单位:秒)
keep-alive-seconds: 300
--- ### 接口文档配置
springdoc:
# 设置对象型参数的展示形式(设为 true 表示将对象型参数平展开,即对象内的属性直接作为参数展示而不是嵌套在对象内,默认 false
# 如果不添加该全局配置,可以在需要如此处理的对象参数类上使用 @ParameterObject
default-flat-param-object: true
# 分组配置
group-configs:
- group: all
paths-to-match: /**
paths-to-exclude:
- /error
- group: auth
display-name: 系统认证
packages-to-scan: ${project.base-package}.webapi.auth
- group: common
display-name: 通用接口
packages-to-scan: ${project.base-package}.webapi.common
- group: system
display-name: 系统管理
packages-to-scan: ${project.base-package}.webapi.system
- group: monitor
display-name: 系统监控
packages-to-scan: ${project.base-package}.webapi.monitor
## 组件配置
components:
# 鉴权配置
security-schemes:
Authorization:
type: HTTP
in: HEADER
name: ${sa-token.token-name}
scheme: ${sa-token.token-prefix}
## 接口文档增强配置
knife4j:
enable: true
setting:
# 是否显示默认的 footer默认 true显示
enable-footer: false
# 是否自定义 footer默认 false非自定义
enable-footer-custom: true
# 自定义 footer 内容,支持 Markdown 语法
footer-custom-content: 'Copyright © 2022-present [${project.contact.name}](${project.contact.url}) ⋅ [${project.name}](${project.url}) v${project.version}'
--- ### Sa-Token 配置
sa-token:
# token 名称(同时也是 cookie 名称)
token-name: Authorization
# token 有效期(单位:秒,默认 30 天,-1 代表永不过期)
timeout: 86400
# token 最低活跃频率(单位:秒,默认 -1代表不限制永不冻结。如果 token 超过此时间没有访问系统就会被冻结)
active-timeout: 1800
# 是否打开自动续签(如果此值为 true框架会在每次直接或间接调用 getLoginId() 时进行一次过期检查与续签操作)
auto-renew: true
# 是否允许同一账号多地同时登录(为 true 时允许一起登录,为 false 时新登录挤掉旧登录)
is-concurrent: true
# 在多人登录同一账号时,是否共用一个 token为 true 时所有登录共用一个 token为 false 时每次登录新建一个 token
is-share: false
# 是否输出操作日志
is-log: false
# JWT 秘钥
jwt-secret-key: asdasdasifhueuiwyurfewbfjsdafjk
## 扩展配置
extension:
enabled: true
enableJwt: true
# 持久层配置
dao.type: REDIS
--- ### MyBatis Plus 配置
mybatis-plus:
# Mapper XML 文件目录配置
mapper-locations: classpath*:/mapper/**/*Mapper.xml
# 类型别名扫描包配置
type-aliases-package: ${project.base-package}.**.model
configuration:
# MyBatis 自动映射策略
# NONE不启用 PARTIAL只对非嵌套 resultMap 自动映射 FULL对所有 resultMap 自动映射
auto-mapping-behavior: PARTIAL
global-config:
banner: true
db-config:
# 主键类型(默认 assign_id表示自行赋值
# auto 代表使用数据库自增策略(需要在表中设置好自增约束)
id-type: ASSIGN_ID
# 逻辑删除字段
logic-delete-field: isDeleted
# 逻辑删除全局值(默认 1表示已删除
logic-delete-value: 1
# 逻辑未删除全局值(默认 0表示未删除
logic-not-delete-value: 0
## 扩展配置
extension:
enabled: true
# Mapper 接口扫描包配置
mapper-package: ${project.base-package}.**.mapper
# ID 生成器配置
id-generator:
type: COSID
# 数据权限配置
data-permission:
enabled: true
# 分页插件配置
pagination:
enabled: true
db-type: MYSQL
--- ### CosId 配置
cosid:
namespace: ${spring.application.name}
machine:
enabled: true
# 机器号分配器
distributor:
type: REDIS
guarder:
# 开启机器号守护
enabled: true
snowflake:
enabled: true
zone-id: Asia/Shanghai
epoch: 1577203200000
share:
# 开启时钟回拨同步
clock-sync: true
friendly: true
provider:
safe-js:
machine-bit: 3
sequence-bit: 9
--- ### 服务器配置
server:
servlet:
# 应用访问路径
context-path: /
## Undertow 服务器配置
undertow:
# HTTP POST 请求内容的大小上限(默认 -1不限制
max-http-post-size: -1
# 以下的配置会影响 buffer这些 buffer 会用于服务器连接的 IO 操作,有点类似 Netty 的池化内存管理
# 每块 buffer的空间大小越小的空间被利用越充分不要设置太大以免影响其他应用合适即可
buffer-size: 512
# 是否分配的直接内存NIO 直接分配的堆外内存)
direct-buffers: true
threads:
# 设置 IO 线程数,它主要执行非阻塞的任务,它们会负责多个连接(默认每个 CPU 核心一个线程)
io: 8
# 阻塞任务线程池,当执行类似 Servlet 请求阻塞操作Undertow 会从这个线程池中取得线程(它的值设置取决于系统的负载)
worker: 256
--- ### Spring 配置
spring:
application:
name: ${project.app-name}
## 环境配置
profiles:
# 启用的环境
active: dev
include:
- generator
main:
# 允许定义重名的 bean 对象覆盖原有的 bean
allow-bean-definition-overriding: true
# 允许循环依赖
allow-circular-references: true
--- ### 健康检查配置
management.health:
mail:
# 关闭邮箱健康检查(邮箱配置错误或邮箱服务器不可用时,健康检查会报错)
enabled: false
webclient:
max-concurrent-requests: 60
spring:
rabbitmq:
host: 45.89.233.228
port: 5672
username: bot
password: xiaomi@123

View File

@ -1,12 +1,28 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
*
* 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.
*/
package com.zayac.admin.agent.mapper; package com.zayac.admin.agent.mapper;
import top.continew.starter.data.mybatis.plus.base.BaseMapper; import top.continew.starter.data.mybatis.plus.base.BaseMapper;
import com.zayac.admin.agent.model.entity.StatsDO; import com.zayac.admin.agent.model.entity.StatsDO;
/** /**
* 代理每日数据 Mapper * 代理每日数据 Mapper
* *
* @author zayac * @author zayac
* @since 2024/06/04 17:10 * @since 2024/06/04 17:10
*/ */
public interface DailyStatsMapper extends BaseMapper<StatsDO> {} public interface DailyStatsMapper extends BaseMapper<StatsDO> {}

View File

@ -1,12 +1,28 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
*
* 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.
*/
package com.zayac.admin.agent.mapper; package com.zayac.admin.agent.mapper;
import top.continew.starter.data.mybatis.plus.base.BaseMapper; import top.continew.starter.data.mybatis.plus.base.BaseMapper;
import com.zayac.admin.agent.model.entity.FinanceDO; import com.zayac.admin.agent.model.entity.FinanceDO;
/** /**
* 代理线财务报 Mapper * 代理线财务报 Mapper
* *
* @author zayac * @author zayac
* @since 2024/06/04 17:14 * @since 2024/06/04 17:14
*/ */
public interface FinanceMapper extends BaseMapper<FinanceDO> {} public interface FinanceMapper extends BaseMapper<FinanceDO> {}

View File

@ -1,12 +1,28 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
*
* 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.
*/
package com.zayac.admin.agent.mapper; package com.zayac.admin.agent.mapper;
import top.continew.starter.data.mybatis.plus.base.BaseMapper; import top.continew.starter.data.mybatis.plus.base.BaseMapper;
import com.zayac.admin.agent.model.entity.FinanceSumDO; import com.zayac.admin.agent.model.entity.FinanceSumDO;
/** /**
* 财务报汇总 Mapper * 财务报汇总 Mapper
* *
* @author zayac * @author zayac
* @since 2024/06/04 17:10 * @since 2024/06/04 17:10
*/ */
public interface FinanceSumMapper extends BaseMapper<FinanceSumDO> {} public interface FinanceSumMapper extends BaseMapper<FinanceSumDO> {}

View File

@ -1,3 +1,19 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
*
* 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.
*/
package com.zayac.admin.agent.model.entity; package com.zayac.admin.agent.model.entity;
import java.io.Serial; import java.io.Serial;

View File

@ -1,3 +1,19 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
*
* 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.
*/
package com.zayac.admin.agent.model.entity; package com.zayac.admin.agent.model.entity;
import java.io.Serial; import java.io.Serial;

View File

@ -1,3 +1,19 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
*
* 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.
*/
package com.zayac.admin.agent.model.entity; package com.zayac.admin.agent.model.entity;
import java.io.Serial; import java.io.Serial;

View File

@ -1,9 +1,24 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
*
* 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.
*/
package com.zayac.admin.agent.model.query; package com.zayac.admin.agent.model.query;
import java.io.Serial; import java.io.Serial;
import java.io.Serializable; import java.io.Serializable;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.math.BigDecimal;
import lombok.Data; import lombok.Data;

View File

@ -1,17 +1,28 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
*
* 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.
*/
package com.zayac.admin.agent.model.query; package com.zayac.admin.agent.model.query;
import java.io.Serial; import java.io.Serial;
import java.io.Serializable; import java.io.Serializable;
import java.time.LocalDateTime;
import java.math.BigDecimal;
import lombok.Data; import lombok.Data;
import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.media.Schema;
import top.continew.starter.data.core.annotation.Query;
import top.continew.starter.data.core.enums.QueryType;
/** /**
* 财务报汇总查询条件 * 财务报汇总查询条件
* *

View File

@ -1,3 +1,19 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
*
* 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.
*/
package com.zayac.admin.agent.model.query; package com.zayac.admin.agent.model.query;
import java.io.Serial; import java.io.Serial;

View File

@ -1,3 +1,19 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
*
* 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.
*/
package com.zayac.admin.agent.model.req; package com.zayac.admin.agent.model.req;
import java.io.Serial; import java.io.Serial;
@ -5,14 +21,10 @@ import java.time.LocalDate;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.math.BigDecimal; import java.math.BigDecimal;
import jakarta.validation.constraints.*;
import lombok.Data; import lombok.Data;
import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.media.Schema;
import org.hibernate.validator.constraints.Length;
import top.continew.starter.extension.crud.model.req.BaseReq; import top.continew.starter.extension.crud.model.req.BaseReq;
/** /**

View File

@ -1,16 +1,28 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
*
* 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.
*/
package com.zayac.admin.agent.model.req; package com.zayac.admin.agent.model.req;
import java.io.Serial; import java.io.Serial;
import java.time.LocalDateTime;
import java.math.BigDecimal; import java.math.BigDecimal;
import lombok.Data; import lombok.Data;
import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.media.Schema;
import org.hibernate.validator.constraints.Length;
import top.continew.starter.extension.crud.model.req.BaseReq; import top.continew.starter.extension.crud.model.req.BaseReq;
/** /**

View File

@ -1,3 +1,19 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
*
* 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.
*/
package com.zayac.admin.agent.model.req; package com.zayac.admin.agent.model.req;
import java.io.Serial; import java.io.Serial;

View File

@ -1,3 +1,19 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
*
* 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.
*/
package com.zayac.admin.agent.model.resp; package com.zayac.admin.agent.model.resp;
import java.io.Serial; import java.io.Serial;

View File

@ -1,3 +1,19 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
*
* 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.
*/
package com.zayac.admin.agent.model.resp; package com.zayac.admin.agent.model.resp;
import java.io.Serial; import java.io.Serial;

View File

@ -1,3 +1,19 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
*
* 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.
*/
package com.zayac.admin.agent.model.resp; package com.zayac.admin.agent.model.resp;
import java.io.Serial; import java.io.Serial;

View File

@ -1,3 +1,19 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
*
* 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.
*/
package com.zayac.admin.agent.model.resp; package com.zayac.admin.agent.model.resp;
import java.io.Serial; import java.io.Serial;

View File

@ -1,3 +1,19 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
*
* 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.
*/
package com.zayac.admin.agent.model.resp; package com.zayac.admin.agent.model.resp;
import java.io.Serial; import java.io.Serial;

View File

@ -1,3 +1,19 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
*
* 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.
*/
package com.zayac.admin.agent.model.resp; package com.zayac.admin.agent.model.resp;
import java.io.Serial; import java.io.Serial;

View File

@ -1,3 +1,19 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
*
* 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.
*/
package com.zayac.admin.agent.service; package com.zayac.admin.agent.service;
import com.zayac.admin.agent.model.entity.FinanceDO; import com.zayac.admin.agent.model.entity.FinanceDO;

View File

@ -1,3 +1,19 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
*
* 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.
*/
package com.zayac.admin.agent.service; package com.zayac.admin.agent.service;
import top.continew.starter.extension.crud.service.BaseService; import top.continew.starter.extension.crud.service.BaseService;

View File

@ -1,3 +1,19 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
*
* 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.
*/
package com.zayac.admin.agent.service; package com.zayac.admin.agent.service;
import com.zayac.admin.agent.model.entity.StatsDO; import com.zayac.admin.agent.model.entity.StatsDO;

View File

@ -1,3 +1,19 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
*
* 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.
*/
package com.zayac.admin.agent.service.impl; package com.zayac.admin.agent.service.impl;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;

View File

@ -1,6 +1,21 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
*
* 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.
*/
package com.zayac.admin.agent.service.impl; package com.zayac.admin.agent.service.impl;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
@ -25,7 +40,7 @@ import java.util.List;
@Service @Service
@RequiredArgsConstructor @RequiredArgsConstructor
public class FinanceServiceImpl extends BaseServiceImpl<FinanceMapper, FinanceDO, FinanceResp, FinanceDetailResp, FinanceQuery, FinanceReq> implements FinanceService { public class FinanceServiceImpl extends BaseServiceImpl<FinanceMapper, FinanceDO, FinanceResp, FinanceDetailResp, FinanceQuery, FinanceReq> implements FinanceService {
public void addAll(List<FinanceDO> financeReqList){ public void addAll(List<FinanceDO> financeReqList) {
baseMapper.insertBatch(financeReqList); baseMapper.insertBatch(financeReqList);
} }

View File

@ -1,3 +1,19 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
*
* 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.
*/
package com.zayac.admin.agent.service.impl; package com.zayac.admin.agent.service.impl;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;

View File

@ -1,3 +1,19 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
*
* 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.
*/
package com.zayac.admin.config; package com.zayac.admin.config;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;

View File

@ -1,3 +1,19 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
*
* 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.
*/
package com.zayac.admin.req; package com.zayac.admin.req;
import com.fasterxml.jackson.annotation.JsonFormat; import com.fasterxml.jackson.annotation.JsonFormat;

View File

@ -1,3 +1,19 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
*
* 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.
*/
package com.zayac.admin.req.team; package com.zayac.admin.req.team;
import com.fasterxml.jackson.annotation.JsonFormat; import com.fasterxml.jackson.annotation.JsonFormat;

View File

@ -1,3 +1,19 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
*
* 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.
*/
package com.zayac.admin.resp.team; package com.zayac.admin.resp.team;
import com.zayac.admin.resp.Pagination; import com.zayac.admin.resp.Pagination;

View File

@ -1,3 +1,19 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
*
* 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.
*/
package com.zayac.admin.resp.team; package com.zayac.admin.resp.team;
import com.zayac.admin.resp.Venue; import com.zayac.admin.resp.Venue;
@ -7,6 +23,7 @@ import lombok.NoArgsConstructor;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.util.List; import java.util.List;
@Data @Data
@NoArgsConstructor @NoArgsConstructor
@AllArgsConstructor @AllArgsConstructor

View File

@ -1,3 +1,19 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
*
* 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.
*/
package com.zayac.admin.resp.team; package com.zayac.admin.resp.team;
import com.zayac.admin.resp.Pagination; import com.zayac.admin.resp.Pagination;

View File

@ -1,3 +1,19 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
*
* 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.
*/
package com.zayac.admin.resp.team; package com.zayac.admin.resp.team;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;

View File

@ -16,20 +16,17 @@
package com.zayac.admin.schedule; package com.zayac.admin.schedule;
import com.zayac.admin.common.enums.DisEnableStatusEnum;
import com.zayac.admin.req.team.TeamInfoReq; import com.zayac.admin.req.team.TeamInfoReq;
import com.zayac.admin.resp.team.Team; import com.zayac.admin.resp.team.Team;
import com.zayac.admin.resp.team.TeamAccount; import com.zayac.admin.resp.team.TeamAccount;
import com.zayac.admin.service.*; import com.zayac.admin.service.*;
import com.zayac.admin.system.model.entity.RoleDO;
import com.zayac.admin.system.model.entity.UserDO;
import com.zayac.admin.system.model.resp.AccountResp; import com.zayac.admin.system.model.resp.AccountResp;
import com.zayac.admin.system.service.AccountService; import com.zayac.admin.system.model.resp.DeptUsersResp;
import com.zayac.admin.system.service.RoleService; import com.zayac.admin.system.model.resp.UserWithRolesAndAccountsResp;
import com.zayac.admin.system.service.UserRoleService; import com.zayac.admin.system.service.*;
import com.zayac.admin.system.service.UserService;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Profile;
import org.springframework.scheduling.annotation.Scheduled; import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@ -37,6 +34,7 @@ import java.time.LocalDate;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.time.LocalTime; import java.time.LocalTime;
import java.time.temporal.TemporalAdjusters; import java.time.temporal.TemporalAdjusters;
import java.util.AbstractMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
@ -51,13 +49,11 @@ import java.util.stream.Collectors;
@Slf4j @Slf4j
@Component @Component
@RequiredArgsConstructor @RequiredArgsConstructor
@Profile("prod")
public class CheckRegAndDep { public class CheckRegAndDep {
private final UserService userService;
private final AccountService accountService;
private final TeamService teamService; private final TeamService teamService;
private final RoleService roleService; private final DeptService deptService;
private final UserRoleService userRoleService;
private final RegistrationService registrationService; private final RegistrationService registrationService;
private final DepositService depositService; private final DepositService depositService;
private final Executor asyncTaskExecutor; private final Executor asyncTaskExecutor;
@ -66,37 +62,56 @@ public class CheckRegAndDep {
private static final long FIXED_DELAY = 60000L; private static final long FIXED_DELAY = 60000L;
@Scheduled(fixedDelay = FIXED_DELAY) @Scheduled(fixedDelay = FIXED_DELAY)
public void CheckRegistrationAndNewDeposit() { public void checkRegistrationAndNewDeposit() {
LocalDate nowDate = LocalDate.now(); LocalDate nowDate = LocalDate.now();
LocalDateTime nowDateTime = LocalDateTime.now(); LocalDateTime nowDateTime = LocalDateTime.now();
RoleDO minister = roleService.getByCode(MINISTER_ROLE_CODE); //查询用户角色为部长的部门所有用户
List<Long> userIds = userRoleService.listUserIdByRoleId(minister.getId()); List<DeptUsersResp> deptWithUsersAndAccounts = deptService.getDeptWithUsersAndAccounts(MINISTER_ROLE_CODE);
userIds.forEach(userId -> { deptWithUsersAndAccounts.forEach(dept -> {
processUser(userService.getById(userId), nowDate, nowDateTime).join(); //根据用户角色对部门用户进行分组
Map<String, List<UserWithRolesAndAccountsResp>> usersByRole = dept.getUsers()
.stream()
.flatMap(user -> user.getRoles()
.stream()
.map(role -> new AbstractMap.SimpleEntry<>(role.getCode(), user)))
.collect(Collectors.groupingByConcurrent(Map.Entry::getKey, Collectors
.mapping(Map.Entry::getValue, Collectors.toList())));
// 获取所有账号的username与用户的映射
Map<String, UserWithRolesAndAccountsResp> accountUsernameToUserMap = dept.getUsers()
.stream()
.flatMap(user -> user.getAccounts()
.stream()
.map(account -> new AbstractMap.SimpleEntry<>(account.getUsername(), user)))
.collect(Collectors.toConcurrentMap(Map.Entry::getKey, Map.Entry::getValue));
var ministerUser = usersByRole.get(MINISTER_ROLE_CODE).get(0);
processUser(ministerUser, accountUsernameToUserMap, nowDate, nowDateTime).join();
}); });
} }
private CompletableFuture<Void> processUser(UserDO minister, LocalDate nowDate, LocalDateTime nowDateTime) { private CompletableFuture<Void> processUser(UserWithRolesAndAccountsResp minister,
List<AccountResp> accounts = accountService.getAccountsByUserId(minister.getId(), DisEnableStatusEnum.ENABLE) Map<String, UserWithRolesAndAccountsResp> accountUsernameToUserMap,
.stream() LocalDate nowDate,
.filter(AccountResp::getIsTeam) LocalDateTime nowDateTime) {
.toList(); //根据总线用户的账号查询数据
List<AccountResp> accounts = minister.getAccounts();
List<CompletableFuture<Void>> futures = accounts.stream() List<CompletableFuture<Void>> futures = accounts.stream()
.map(account -> processTeamAccount(minister, account, nowDate, nowDateTime)) .map(account -> processTeamAccount(minister, accountUsernameToUserMap, account, nowDate, nowDateTime))
.toList(); .toList();
return CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])); return CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]));
} }
private CompletableFuture<Void> processTeamAccount(UserDO minister, private CompletableFuture<Void> processTeamAccount(UserWithRolesAndAccountsResp minister,
Map<String, UserWithRolesAndAccountsResp> accountUsernameToUserMap,
AccountResp account, AccountResp account,
LocalDate nowDate, LocalDate nowDate,
LocalDateTime nowDateTime) { LocalDateTime nowDateTime) {
TeamInfoReq teamInfoReq = TeamInfoReq.builder() TeamInfoReq teamInfoReq = TeamInfoReq.builder()
.startDate(nowDateTime.with(TemporalAdjusters.firstDayOfMonth()).with(LocalTime.MIN)) .startDate(nowDateTime.with(TemporalAdjusters.firstDayOfMonth()).with(LocalTime.MIN))
.endDate(nowDateTime.with(TemporalAdjusters.lastDayOfMonth()).with(LocalTime.MAX)) .endDate(nowDateTime.with(TemporalAdjusters.lastDayOfMonth()).with(LocalTime.MAX))
.build(); .build();
CompletableFuture<Team> teamFuture = teamService.getLatestTeamInfoAsync(account, teamInfoReq); CompletableFuture<Team> teamFuture = teamService.getLatestTeamInfoAsync(account, teamInfoReq);
Team prevTeamInfo = teamService.getPreviousTeamInfo(account); Team prevTeamInfo = teamService.getPreviousTeamInfo(account);
@ -104,22 +119,21 @@ public class CheckRegAndDep {
log.info("Previous Team Info: {}", prevTeamInfo); log.info("Previous Team Info: {}", prevTeamInfo);
log.info("Current Team Info: {}", currentTeamInfo); log.info("Current Team Info: {}", currentTeamInfo);
Map<String, TeamAccount> teamAccountMap = currentTeamInfo.getList() Map<String, TeamAccount> teamAccountMap = currentTeamInfo.getList()
.stream() .stream()
.collect(Collectors.toMap(TeamAccount::getAgentName, Function.identity())); .collect(Collectors.toMap(TeamAccount::getAgentName, Function.identity()));
CompletableFuture<Void> registrationProcess = registrationService CompletableFuture<Void> registrationProcess = registrationService
.processRegistration(minister, account, currentTeamInfo, prevTeamInfo, nowDate, teamAccountMap, asyncTaskExecutor) .processRegistration(minister, account, accountUsernameToUserMap, teamAccountMap, currentTeamInfo, prevTeamInfo, nowDate, asyncTaskExecutor)
.thenRunAsync(() -> log.info("Registration process completed"), asyncTaskExecutor); .thenRunAsync(() -> log.info("Registration process completed"), asyncTaskExecutor);
CompletableFuture<Void> depositProcess = depositService CompletableFuture<Void> depositProcess = depositService
.processDeposits(minister, account, currentTeamInfo, prevTeamInfo, nowDate, nowDateTime, asyncTaskExecutor) .processDeposits(minister, accountUsernameToUserMap, account, currentTeamInfo, prevTeamInfo, nowDate, nowDateTime, asyncTaskExecutor)
.thenRunAsync(() -> log.info("Deposit process completed"), asyncTaskExecutor); .thenRunAsync(() -> log.info("Deposit process completed"), asyncTaskExecutor);
return CompletableFuture.allOf(registrationProcess, depositProcess) return CompletableFuture.allOf(registrationProcess, depositProcess).thenRunAsync(() -> {
.thenRunAsync(() -> { teamService.updateTeamInfo(account, currentTeamInfo);
teamService.updateTeamInfo(account, currentTeamInfo); log.info("Team info updated");
log.info("Team info updated"); }, asyncTaskExecutor);
}, asyncTaskExecutor);
}, asyncTaskExecutor); }, asyncTaskExecutor);
} }
} }

View File

@ -17,6 +17,7 @@
package com.zayac.admin.schedule; package com.zayac.admin.schedule;
import cn.hutool.core.bean.BeanUtil; import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.StrUtil;
import com.zayac.admin.agent.model.entity.FinanceDO; import com.zayac.admin.agent.model.entity.FinanceDO;
import com.zayac.admin.agent.model.entity.StatsDO; import com.zayac.admin.agent.model.entity.StatsDO;
@ -34,13 +35,14 @@ import com.zayac.admin.req.team.TeamMemberReq;
import com.zayac.admin.resp.*; import com.zayac.admin.resp.*;
import com.zayac.admin.resp.team.*; import com.zayac.admin.resp.team.*;
import com.zayac.admin.service.*; import com.zayac.admin.service.*;
import com.zayac.admin.system.model.entity.RoleDO;
import com.zayac.admin.system.model.entity.UserDO;
import com.zayac.admin.system.model.resp.AccountResp; import com.zayac.admin.system.model.resp.AccountResp;
import com.zayac.admin.system.model.resp.DeptUsersResp;
import com.zayac.admin.system.model.resp.UserWithRolesAndAccountsResp;
import com.zayac.admin.system.service.*; import com.zayac.admin.system.service.*;
import com.zayac.admin.utils.TableFormatter; import com.zayac.admin.utils.TableFormatter;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Profile;
import org.springframework.core.ParameterizedTypeReference; import org.springframework.core.ParameterizedTypeReference;
import org.springframework.scheduling.annotation.Scheduled; import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@ -60,12 +62,10 @@ import java.util.stream.Collectors;
@Component @Component
@RequiredArgsConstructor @RequiredArgsConstructor
@Slf4j @Slf4j
@Profile("prod")
public class DailyReport { public class DailyReport {
private final UserService userService;
private final TeamService teamService; private final TeamService teamService;
private final RoleService roleService; private final DeptService deptService;
private final UserRoleService userRoleService;
private final AccountService accountService;
private final TelegramMessageService telegramMessageService; private final TelegramMessageService telegramMessageService;
private final AgentDataVisualListService agentDataVisualListService; private final AgentDataVisualListService agentDataVisualListService;
private final StatsService statsService; private final StatsService statsService;
@ -76,20 +76,93 @@ public class DailyReport {
private final Executor asyncTaskExecutor; private final Executor asyncTaskExecutor;
private static final String MINISTER_ROLE_CODE = "minister"; private static final String MINISTER_ROLE_CODE = "minister";
private static final String SEO_TEAM_LEADER_ROLE_CODE = "seo_team_leader";
private static final String ASSISTANT_ROLE_CODE = "assistant";
private static final String SEO_ROLE_CODE = "seo";
@Scheduled(cron = "0 40 11,14,17,21 * * ?") @Scheduled(cron = "0 40 11,14,17,21 * * ?")
public void teamAccountDailyReport() { public void teamAccountDailyReport() {
LocalDateTime nowDateTime = LocalDateTime.now(); LocalDateTime nowDateTime = LocalDateTime.now();
LocalDate nowDate = LocalDate.now(); LocalDate nowDate = LocalDate.now();
sendDailyReport(nowDate, nowDate.atStartOfDay(), nowDateTime); //查询部门下的所有用户
List<DeptUsersResp> deptWithUsersAndAccounts = deptService.getDeptWithUsersAndAccounts(MINISTER_ROLE_CODE);
deptWithUsersAndAccounts.forEach(dept -> {
//根据用户角色对部门用户进行分组
Map<String, List<UserWithRolesAndAccountsResp>> usersByRole = dept.getUsers()
.stream()
.flatMap(user -> user.getRoles()
.stream()
.map(role -> new AbstractMap.SimpleEntry<>(role.getCode(), user)))
.collect(Collectors.groupingByConcurrent(Map.Entry::getKey, Collectors
.mapping(Map.Entry::getValue, Collectors.toList())));
var ministerUser = usersByRole.get(MINISTER_ROLE_CODE).get(0);
//获取账号不为空的用户
var deptUsers = dept.getUsers().stream().filter(user -> CollUtil.isNotEmpty(user.getAccounts())).toList();
var assistants = usersByRole.get(ASSISTANT_ROLE_CODE);
sendDailyReport(nowDate, nowDate.atStartOfDay(), nowDateTime, ministerUser, assistants, deptUsers);
});
} }
@Scheduled(cron = "0 15 0 * * ?") @Scheduled(cron = "0 15 0 * * ?")
public void dailySummarize() { public void dailySummarize() {
LocalDate yesterday = LocalDate.now().minusDays(1); LocalDate yesterday = LocalDate.now().minusDays(1);
sendDailyReport(yesterday, yesterday.atStartOfDay(), LocalDateTime.of(yesterday, LocalTime.MAX)); //查询部门下的所有用户
getPayFailedMember(yesterday); List<DeptUsersResp> deptWithUsersAndAccounts = deptService.getDeptWithUsersAndAccounts(MINISTER_ROLE_CODE);
saveData(yesterday);
deptWithUsersAndAccounts.forEach(dept -> {
//根据用户角色对部门用户进行分组
Map<String, List<UserWithRolesAndAccountsResp>> usersByRole = dept.getUsers()
.stream()
.flatMap(user -> user.getRoles()
.stream()
.map(role -> new AbstractMap.SimpleEntry<>(role.getCode(), user)))
.collect(Collectors.groupingByConcurrent(Map.Entry::getKey, Collectors
.mapping(Map.Entry::getValue, Collectors.toList())));
// 获取所有账号的username与用户的映射
Map<String, UserWithRolesAndAccountsResp> accountUsernameToUserMap = dept.getUsers()
.stream()
.flatMap(user -> user.getAccounts()
.stream()
.map(account -> new AbstractMap.SimpleEntry<>(account.getUsername(), user)))
.collect(Collectors.toConcurrentMap(Map.Entry::getKey, Map.Entry::getValue));
var ministerUser = usersByRole.get(MINISTER_ROLE_CODE).get(0);
//获取账号不为空的用户
var deptUsers = dept.getUsers().stream().filter(user -> CollUtil.isNotEmpty(user.getAccounts())).toList();
var assistants = usersByRole.get(ASSISTANT_ROLE_CODE);
sendDailyReport(yesterday, yesterday.atStartOfDay(), LocalDateTime
.of(yesterday, LocalTime.MAX), ministerUser, assistants, deptUsers);
getPayFailedMember(ministerUser, accountUsernameToUserMap, yesterday);
saveData(ministerUser, deptUsers, yesterday);
});
}
// 一小时发送一次
@Scheduled(cron = "0 0 * * * ?")
//@Scheduled(fixedDelay = 6000L)
public void generateTeamReportTask() {
LocalDateTime nowDateTime = LocalDateTime.now();
LocalDate nowDate = LocalDate.now();
//查询部门下的所有用户
List<DeptUsersResp> deptWithUsersAndAccounts = deptService.getDeptWithUsersAndAccounts(MINISTER_ROLE_CODE);
deptWithUsersAndAccounts.forEach(dept -> {
//根据用户角色对部门用户进行分组
Map<String, List<UserWithRolesAndAccountsResp>> usersByRole = dept.getUsers()
.stream()
.flatMap(user -> user.getRoles()
.stream()
.map(role -> new AbstractMap.SimpleEntry<>(role.getCode(), user)))
.collect(Collectors.groupingByConcurrent(Map.Entry::getKey, Collectors
.mapping(Map.Entry::getValue, Collectors.toList())));
var userWithRolesAndAccountsResps = usersByRole.get(MINISTER_ROLE_CODE);
userWithRolesAndAccountsResps.forEach(ministerUser -> generateAndSendTeamReport(ministerUser, nowDate
.atStartOfDay(), nowDateTime, null));
});
} }
/** /**
@ -97,137 +170,131 @@ public class DailyReport {
* *
* @param date 日期 * @param date 日期
*/ */
private void getPayFailedMember(LocalDate date) { private void getPayFailedMember(UserWithRolesAndAccountsResp ministerUser,
RoleDO minister = roleService.getByCode(MINISTER_ROLE_CODE); Map<String, UserWithRolesAndAccountsResp> accountUsernameToUserMap,
List<Long> userIds = userRoleService.listUserIdByRoleId(minister.getId()); LocalDate date) {
TeamMemberReq memberListReq = TeamMemberReq.builder() TeamMemberReq memberListReq = TeamMemberReq.builder()
.registerStartDate(date) .registerStartDate(date)
.registerEndDate(date) .registerEndDate(date)
.startDate(date) .startDate(date)
.endDate(date) .endDate(date)
.registerSort(1) .registerSort(1)
.status(1) .status(1)
.pageSize(100) .pageSize(100)
.build(); .build();
userIds.forEach(userId -> { List<CompletableFuture<List<TeamMember>>> futureList = new ArrayList<>();
List<CompletableFuture<List<TeamMember>>> futureList = new ArrayList<>(); ministerUser.getAccounts().forEach(account -> {
UserDO ministerUser = userService.getById(userId); CompletableFuture<MemberPagination<List<TeamMember>>> memberPaginationCompletableFuture = completableFutureWebClientService
List<AccountResp> accounts = accountService.getAccountsByUserId(userId, DisEnableStatusEnum.ENABLE) .fetchDataForAccount(account, ApiPathConstants.MEMBER_TEAM_LIST_URL, memberListReq, new ParameterizedTypeReference<>() {
.stream()
.filter(AccountResp::getIsTeam)
.toList();
accounts.forEach(account -> {
CompletableFuture<MemberPagination<List<TeamMember>>> memberPaginationCompletableFuture = completableFutureWebClientService
.fetchDataForAccount(account, ApiPathConstants.MEMBER_TEAM_LIST_URL, memberListReq, new ParameterizedTypeReference<>() {
});
CompletableFuture<List<TeamMember>> teamMembersFuture = memberPaginationCompletableFuture.thenApply(MemberPagination::getList)
.thenApplyAsync(members -> members.stream()
.filter(member -> member.getDeposit() != null && member.getDeposit().compareTo(BigDecimal.ZERO) == 0)
.collect(Collectors.toList()), asyncTaskExecutor)
.thenComposeAsync(membersWithoutDep -> {
List<CompletableFuture<TeamMember>> memberFutures = membersWithoutDep.stream()
.map(memberWithoutDep -> {
PayRecordListReq req = PayRecordListReq.builder()
.startDate(date)
.endDate(date)
.pageSize(100)
.id(memberWithoutDep.getId())
.build();
CompletableFuture<PayRecordList<List<PayRecord>>> completableFuture = completableFutureWebClientService
.fetchDataForAccount(account, ApiPathConstants.PAY_RECORD_LIST_URL, req, new ParameterizedTypeReference<>() {
});
return completableFuture.thenApplyAsync(pagination -> {
if (pagination.getOrderAmountTotal().compareTo(BigDecimal.ZERO) > 0
&& pagination.getScoreAmountTotal().compareTo(BigDecimal.ZERO) == 0) {
return memberWithoutDep;
} else {
return null;
}
}, asyncTaskExecutor);
}).toList();
return CompletableFuture.allOf(memberFutures.toArray(new CompletableFuture[0]))
.thenApply(v -> memberFutures.stream()
.map(CompletableFuture::join)
.filter(Objects::nonNull)
.collect(Collectors.toList()));
}, asyncTaskExecutor)
.thenApplyAsync(membersWithoutDep -> {
// 发送给每个account关联的user用户
if (!membersWithoutDep.isEmpty()) {
Map<String, List<TeamMember>> groupByTopAgentName = membersWithoutDep.stream()
.collect(Collectors.groupingBy(TeamMember::getTopAgentName));
groupByTopAgentName.forEach((accountName, accountMembers) -> {
String notification = telegramMessageService
.buildFailedPayMessage(accountName, accountMembers);
UserDO currUser = accountService.getUserByAccountUsername(accountName);
if (currUser != null && DisEnableStatusEnum.ENABLE.equals(currUser.getNeedNotify())) {
String botToken = StrUtil.isEmpty(currUser.getBotToken()) ? ministerUser.getBotToken() : currUser.getBotToken();
telegramMessageService.sendMessage(botToken, currUser.getRegAndDepIds(), notification);
}
});
}
return membersWithoutDep;
}, asyncTaskExecutor);
futureList.add(teamMembersFuture);
});
CompletableFuture<Void> allFutures = CompletableFuture.allOf(futureList.toArray(new CompletableFuture[0]));
allFutures.thenRunAsync(() -> {
// 主线下的所有的存款失败用户
List<TeamMember> allTeamMembers = futureList.stream()
.map(CompletableFuture::join)
.flatMap(List::stream)
.toList();
Map<String, List<TeamMember>> groupByTopAgentName = new TreeMap<>(allTeamMembers.stream()
.collect(Collectors.groupingBy(TeamMember::getTopAgentName)));
StringBuilder combinedNotification = new StringBuilder();
groupByTopAgentName.forEach((accountName, accountMembers) -> {
String notification = telegramMessageService
.buildFailedPayMessage(accountName, accountMembers);
combinedNotification.append(notification).append("\n");
}); });
telegramMessageService
.sendMessage("6013830443:AAHUOS4v6Ln19ziZkH-L28-HZQLJrGcvhto", 6054562838L, combinedNotification.toString());
if (ministerUser != null && DisEnableStatusEnum.ENABLE.equals(ministerUser.getNeedNotify())) {
telegramMessageService.sendMessage(ministerUser.getBotToken(), ministerUser.getReportIds(), combinedNotification.toString());
}
}, asyncTaskExecutor).exceptionally(ex -> { CompletableFuture<List<TeamMember>> teamMembersFuture = memberPaginationCompletableFuture
log.error("Error collecting and processing data", ex); .thenApply(MemberPagination::getList)
return null; .thenApplyAsync(members -> members.stream()
}); .filter(member -> member.getDeposit() != null && member.getDeposit()
.compareTo(BigDecimal.ZERO) == 0)
.collect(Collectors.toList()), asyncTaskExecutor)
.thenComposeAsync(membersWithoutDep -> {
List<CompletableFuture<TeamMember>> memberFutures = membersWithoutDep.stream()
.map(memberWithoutDep -> {
PayRecordListReq req = PayRecordListReq.builder()
.startDate(date)
.endDate(date)
.pageSize(100)
.id(memberWithoutDep.getId())
.build();
CompletableFuture<PayRecordList<List<PayRecord>>> completableFuture = completableFutureWebClientService
.fetchDataForAccount(account, ApiPathConstants.PAY_RECORD_LIST_URL, req, new ParameterizedTypeReference<>() {
});
return completableFuture.thenApplyAsync(pagination -> {
if (pagination.getOrderAmountTotal().compareTo(BigDecimal.ZERO) > 0 && pagination
.getScoreAmountTotal()
.compareTo(BigDecimal.ZERO) == 0) {
return memberWithoutDep;
} else {
return null;
}
}, asyncTaskExecutor);
})
.toList();
return CompletableFuture.allOf(memberFutures.toArray(new CompletableFuture[0]))
.thenApply(v -> memberFutures.stream()
.map(CompletableFuture::join)
.filter(Objects::nonNull)
.collect(Collectors.toList()));
}, asyncTaskExecutor)
.thenApplyAsync(membersWithoutDep -> {
// 发送给每个account关联的user用户
if (!membersWithoutDep.isEmpty()) {
Map<String, List<TeamMember>> groupByTopAgentName = membersWithoutDep.stream()
.collect(Collectors.groupingBy(TeamMember::getTopAgentName));
groupByTopAgentName.forEach((accountName, accountMembers) -> {
String notification = telegramMessageService
.buildFailedPayMessage(accountName, accountMembers);
UserWithRolesAndAccountsResp currUser = accountUsernameToUserMap.get(accountName);
if (DisEnableStatusEnum.ENABLE.equals(currUser.getNeedNotify())) {
String botToken = StrUtil.isEmpty(currUser.getBotToken())
? ministerUser.getBotToken()
: currUser.getBotToken();
telegramMessageService.sendMessage(botToken, currUser.getRegAndDepIds(), notification);
}
});
}
return membersWithoutDep;
}, asyncTaskExecutor);
futureList.add(teamMembersFuture);
}); });
}
CompletableFuture<Void> allFutures = CompletableFuture.allOf(futureList.toArray(new CompletableFuture[0]));
private void sendDailyReport(LocalDate reportDate, LocalDateTime startDateTime, LocalDateTime endDateTime) { allFutures.thenRunAsync(() -> {
RoleDO minister = roleService.getByCode(MINISTER_ROLE_CODE); // 主线下的所有的存款失败用户
List<Long> userIds = userRoleService.listUserIdByRoleId(minister.getId()); List<TeamMember> allTeamMembers = futureList.stream()
.map(CompletableFuture::join)
userIds.forEach(userId -> { .flatMap(List::stream)
UserDO ministerUser = userService.getById(userId); .toList();
List<UserDO> deptUsers = userService.getByDeptId(DisEnableStatusEnum.ENABLE, ministerUser.getDeptId()); Map<String, List<TeamMember>> groupByTopAgentName = new TreeMap<>(allTeamMembers.stream()
.collect(Collectors.groupingBy(TeamMember::getTopAgentName)));
List<CompletableFuture<Void>> tasks = new ArrayList<>(); StringBuilder combinedNotification = new StringBuilder();
groupByTopAgentName.forEach((accountName, accountMembers) -> {
if (ministerUser.getNeedNotify() == DisEnableStatusEnum.ENABLE) { String notification = telegramMessageService.buildFailedPayMessage(accountName, accountMembers);
tasks.add(generateAndSendTeamReport(ministerUser, startDateTime, endDateTime)); combinedNotification.append(notification).append("\n");
});
telegramMessageService
.sendMessage("6013830443:AAHUOS4v6Ln19ziZkH-L28-HZQLJrGcvhto", 6054562838L, combinedNotification
.toString());
if (DisEnableStatusEnum.ENABLE.equals(ministerUser.getNeedNotify())) {
telegramMessageService.sendMessage(ministerUser.getBotToken(), ministerUser
.getReportIds(), combinedNotification.toString());
} }
AgentDataVisualListReq agentDataVisualListReq = AgentDataVisualListReq.builder() }, asyncTaskExecutor).exceptionally(ex -> {
.monthDate(reportDate) log.error("Error collecting and processing data", ex);
.build(); return null;
deptUsers.forEach(deptUser -> tasks.add(processDeptUser(deptUser, agentDataVisualListReq, reportDate, ministerUser)));
CompletableFuture<Void> allTasks = CompletableFuture.allOf(tasks.toArray(new CompletableFuture[0]));
allTasks.join();
}); });
}
private void sendDailyReport(LocalDate reportDate,
LocalDateTime startDateTime,
LocalDateTime endDateTime,
UserWithRolesAndAccountsResp ministerUser,
List<UserWithRolesAndAccountsResp> assistants,
List<UserWithRolesAndAccountsResp> deptUsers) {
List<CompletableFuture<Void>> tasks = new ArrayList<>();
tasks.add(generateAndSendTeamReport(ministerUser, startDateTime, endDateTime, assistants));
AgentDataVisualListReq agentDataVisualListReq = AgentDataVisualListReq.builder().monthDate(reportDate).build();
deptUsers.forEach(deptUser -> tasks
.add(processDeptUser(deptUser, ministerUser, agentDataVisualListReq, reportDate)));
CompletableFuture<Void> allTasks = CompletableFuture.allOf(tasks.toArray(new CompletableFuture[0]));
allTasks.join();
} }
/** /**
@ -239,44 +306,57 @@ public class DailyReport {
* @return CompletableFuture<Void> * @return CompletableFuture<Void>
*/ */
private CompletableFuture<Void> generateAndSendTeamReport(UserDO ministerUser, LocalDateTime private CompletableFuture<Void> generateAndSendTeamReport(UserWithRolesAndAccountsResp ministerUser,
startDateTime, LocalDateTime endDateTime) { LocalDateTime startDateTime,
LocalDateTime endDateTime,
List<UserWithRolesAndAccountsResp> assistants) {
return CompletableFuture.runAsync(() -> { return CompletableFuture.runAsync(() -> {
List<String[]> rows = new ArrayList<>(); List<String[]> rows = new ArrayList<>();
rows.add(new String[]{"平台", "注册", "新增", "转化率"}); List<AccountResp> accounts = ministerUser.getAccounts();
rows.add(new String[]{"----", "----", "----", "----"});
List<AccountResp> accounts = accountService.getAccountsByUserId(ministerUser.getId(), DisEnableStatusEnum.ENABLE)
.stream()
.filter(AccountResp::getIsTeam)
.toList();
int[] totals = {0, 0}; int[] totals = {0, 0};
TeamInfoReq teamInfoReq = TeamInfoReq.builder().startDate(startDateTime).endDate(endDateTime).build(); TeamInfoReq teamInfoReq = TeamInfoReq.builder().startDate(startDateTime).endDate(endDateTime).build();
List<CompletableFuture<Void>> futures = accounts.stream() List<CompletableFuture<Void>> futures = accounts.stream()
.map(accountResp -> teamService.getLatestTeamInfoAsync(accountResp, teamInfoReq) .map(accountResp -> teamService.getLatestTeamInfoAsync(accountResp, teamInfoReq)
.thenAcceptAsync(team -> { .thenAcceptAsync(team -> {
int totalNewMember = team.getList().stream().mapToInt(TeamAccount::getSubMemberNum).sum(); int totalNewMember = team.getList().stream().mapToInt(TeamAccount::getSubMemberNum).sum();
int totalNewFirstDeposit = team.getList().stream().mapToInt(TeamAccount::getFirstDepositNum).sum(); int totalNewFirstDeposit = team.getList()
synchronized (totals) { .stream()
totals[0] += totalNewMember; .mapToInt(TeamAccount::getFirstDepositNum)
totals[1] += totalNewFirstDeposit; .sum();
} synchronized (totals) {
String percent = getPercent(totalNewFirstDeposit, totalNewMember); totals[0] += totalNewMember;
synchronized (rows) { totals[1] += totalNewFirstDeposit;
rows.add(new String[]{accountResp.getPlatformName(), String.valueOf(totalNewMember), String.valueOf(totalNewFirstDeposit), percent}); }
} String percent = getPercent(totalNewFirstDeposit, totalNewMember);
}, asyncTaskExecutor) synchronized (rows) {
) rows.add(new String[] {accountResp.getPlatformName(), String.valueOf(totalNewMember), String
.toList(); .valueOf(totalNewFirstDeposit), percent});
}
}, asyncTaskExecutor))
.toList();
CompletableFuture<Void> allFutures = CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])); CompletableFuture<Void> allFutures = CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]));
allFutures.join(); allFutures.join();
// rows 列表进行排序
rows.sort(Comparator.comparing((String[] row) -> row[0].length()).thenComparing(row -> row[0]));
rows.add(new String[]{"总注册", String.valueOf(totals[0]), String.valueOf(totals[1]), getPercent(totals[1], totals[0])}); rows.add(new String[] {"总计", String.valueOf(totals[0]), String.valueOf(totals[1]),
getPercent(totals[1], totals[0])});
String message = TableFormatter.formatTableAsHtml(rows); String message = TableFormatter.formatTableAsHtml(rows);
telegramMessageService.sendMessage(ministerUser.getBotToken(), ministerUser.getReportIds(), message); if (ministerUser.getNeedNotify() == DisEnableStatusEnum.ENABLE) {
telegramMessageService.sendMessage(ministerUser.getBotToken(), ministerUser.getReportIds(), message);
}
telegramMessageService.sendMessage("6013830443:AAHUOS4v6Ln19ziZkH-L28-HZQLJrGcvhto", 6054562838L, message);
//发送消息给助理
if (!CollUtil.isEmpty(assistants)) {
assistants.forEach(assistant -> {
if (assistant.getNeedNotify() == DisEnableStatusEnum.ENABLE) {
telegramMessageService.sendMessage(assistant.getBotToken(), assistant.getReportIds(), message);
}
});
}
}, asyncTaskExecutor).exceptionally(ex -> { }, asyncTaskExecutor).exceptionally(ex -> {
log.error("Error generating and sending team report", ex); log.error("Error generating and sending team report", ex);
return null; return null;
@ -292,48 +372,46 @@ public class DailyReport {
* @param ministerUser 上级 * @param ministerUser 上级
* @return CompletableFuture<Void> * @return CompletableFuture<Void>
*/ */
private CompletableFuture<Void> processDeptUser(UserDO deptUser, AgentDataVisualListReq private CompletableFuture<Void> processDeptUser(UserWithRolesAndAccountsResp deptUser,
agentDataVisualListReq, LocalDate reportDate, UserDO ministerUser) { UserWithRolesAndAccountsResp ministerUser,
AgentDataVisualListReq agentDataVisualListReq,
LocalDate reportDate) {
return CompletableFuture.runAsync(() -> { return CompletableFuture.runAsync(() -> {
List<AccountResp> currUserAccounts = accountService.getAccountsByUserId(deptUser.getId(), DisEnableStatusEnum.ENABLE) List<AccountResp> currUserAccounts = deptUser.getAccounts();
.stream()
.filter(accountResp -> !accountResp.getIsTeam())
.toList();
List<CompletableFuture<Statics>> futures = currUserAccounts.stream() List<CompletableFuture<Statics>> futures = currUserAccounts.stream()
.map(currAccount -> agentDataVisualListService.getAgentDataVisualList(currAccount, agentDataVisualListReq) .map(currAccount -> agentDataVisualListService
.thenApplyAsync(agentData -> agentData.getCurData() .getAgentDataVisualList(currAccount, agentDataVisualListReq)
.stream() .thenApplyAsync(agentData -> agentData.getCurData()
.filter(data -> data.getStaticsDate().equals(reportDate)) .stream()
.findFirst() .filter(data -> data.getStaticsDate().equals(reportDate))
.orElseThrow(() -> new BusinessException("No data found for report date")) .findFirst()
) .orElseThrow(() -> new BusinessException("No data found for report date")))
.exceptionally(ex -> { .exceptionally(ex -> {
log.error("Error fetching data for account {}: {}", currAccount.getId(), ex.getMessage()); log.error("Error fetching data for account {}: {}", currAccount.getId(), ex.getMessage());
return null; return null;
}) }))
) .toList();
.toList();
CompletableFuture<Void> userStaticsFuture = CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])); CompletableFuture<Void> userStaticsFuture = CompletableFuture.allOf(futures
.toArray(new CompletableFuture[0]));
userStaticsFuture.thenRunAsync(() -> { userStaticsFuture.thenRunAsync(() -> {
List<Statics> agentDataList = futures.stream() List<Statics> agentDataList = futures.stream().map(future -> {
.map(future -> { try {
try { return future.join();
return future.join(); } catch (Exception ex) {
} catch (Exception ex) { log.error("Error joining future", ex);
log.error("Error joining future", ex); return null;
return null; }
} }).filter(Objects::nonNull).collect(Collectors.toList());
})
.filter(Objects::nonNull)
.collect(Collectors.toList());
// 构造消息体 // 构造消息体
String message = telegramMessageService.buildDailyReportMessage(agentDataList); String message = telegramMessageService.buildDailyReportMessage(agentDataList);
if (StrUtil.isNotBlank(message) && deptUser.getNeedNotify() == DisEnableStatusEnum.ENABLE) { if (StrUtil.isNotBlank(message) && deptUser.getNeedNotify() == DisEnableStatusEnum.ENABLE) {
String botToken = StrUtil.isEmpty(deptUser.getBotToken()) ? ministerUser.getBotToken() : deptUser.getBotToken(); String botToken = StrUtil.isEmpty(deptUser.getBotToken())
? ministerUser.getBotToken()
: deptUser.getBotToken();
telegramMessageService.sendMessage(botToken, deptUser.getReportIds(), message); telegramMessageService.sendMessage(botToken, deptUser.getReportIds(), message);
} }
}, asyncTaskExecutor).exceptionally(ex -> { }, asyncTaskExecutor).exceptionally(ex -> {
@ -346,8 +424,9 @@ public class DailyReport {
}); });
} }
private void saveData(UserWithRolesAndAccountsResp ministerUser,
private void saveData(LocalDate reportDate) { List<UserWithRolesAndAccountsResp> deptUsers,
LocalDate reportDate) {
// 获取传入年月 // 获取传入年月
YearMonth inputYearMonth = YearMonth.from(reportDate); YearMonth inputYearMonth = YearMonth.from(reportDate);
@ -356,95 +435,90 @@ public class DailyReport {
YearMonth previousYearMonth = currentYearMonth.minusMonths(1); YearMonth previousYearMonth = currentYearMonth.minusMonths(1);
if (inputYearMonth.equals(currentYearMonth) || inputYearMonth.equals(previousYearMonth)) { if (inputYearMonth.equals(currentYearMonth) || inputYearMonth.equals(previousYearMonth)) {
RoleDO minister = roleService.getByCode(MINISTER_ROLE_CODE);
List<Long> userIds = userRoleService.listUserIdByRoleId(minister.getId());
List<CompletableFuture<Void>> tasks = new ArrayList<>(); List<CompletableFuture<Void>> tasks = new ArrayList<>();
userIds.forEach(userId -> { TeamFinanceReq teamFinanceReq = TeamFinanceReq.builder()
UserDO ministerUser = userService.getById(userId); .pageNum(1)
List<UserDO> deptUsers = userService.getByDeptId(DisEnableStatusEnum.ENABLE, ministerUser.getDeptId()); .pageSize(999)
TeamFinanceReq teamFinanceReq = TeamFinanceReq.builder() .commissionDate(reportDate)
.pageNum(1) .build();
.pageSize(999)
.commissionDate(reportDate) // 异步处理 ministerUserAccounts
.build(); CompletableFuture<Void> ministerAccountsFuture = CompletableFuture.runAsync(() -> {
List<AccountResp> ministerUserAccounts = accountService.getAccountsByUserId(ministerUser.getId(), DisEnableStatusEnum.ENABLE) List<CompletableFuture<Void>> accountFutures = ministerUser.getAccounts()
.stream() .stream()
.filter(AccountResp::getIsTeam) .map(accountResp -> completableFutureFinanceService.getTeamFinance(accountResp, teamFinanceReq)
.thenAcceptAsync(financePagination -> {
List<FinanceDO> financeReqList = financePagination.getList().stream().map(finance -> {
FinanceDO financeDO = new FinanceDO();
BeanUtil.copyProperties(finance, financeDO);
return financeDO;
}).toList();
financeService.addAll(financeReqList);
FinanceSumReq financeSumReq = new FinanceSumReq();
BeanUtil.copyProperties(financePagination.getTotalSumVo(), financeSumReq);
financeSumService.add(financeSumReq);
}, asyncTaskExecutor)
.exceptionally(ex -> {
log.error("Error processing minister accounts for account {}", accountResp
.getUsername(), ex);
return null;
}))
.toList();
CompletableFuture.allOf(accountFutures.toArray(new CompletableFuture[0])).join();
}, asyncTaskExecutor).exceptionally(ex -> {
log.error("Error processing minister accounts", ex);
return null;
});
tasks.add(ministerAccountsFuture);
// 异步处理 deptUsers
AgentDataVisualListReq agentDataVisualListReq = AgentDataVisualListReq.builder()
.monthDate(reportDate)
.build();
deptUsers.forEach(deptUser -> {
CompletableFuture<Void> deptUserFuture = CompletableFuture.runAsync(() -> {
List<AccountResp> currUserAccounts = deptUser.getAccounts();
List<CompletableFuture<StatsDO>> futures = currUserAccounts.stream()
.map(currAccount -> agentDataVisualListService
.getAgentDataVisualList(currAccount, agentDataVisualListReq)
.thenApplyAsync(agentData -> {
Statics statics = agentData.getCurData()
.stream()
.filter(data -> data.getStaticsDate().equals(reportDate))
.findFirst()
.orElseThrow(() -> new BusinessException("No data found for report date"));
StatsDO statsDO = new StatsDO();
BeanUtil.copyProperties(statics, statsDO);
return statsDO;
}, asyncTaskExecutor)
.exceptionally(ex -> {
log.error("Error fetching data for account {}: {}", currAccount.getId(), ex
.getMessage());
return null;
}))
.toList(); .toList();
// 异步处理 ministerUserAccounts List<StatsDO> list = futures.stream()
CompletableFuture<Void> ministerAccountsFuture = CompletableFuture.runAsync(() -> .map(CompletableFuture::join)
ministerUserAccounts.parallelStream().forEach(accountResp -> { .filter(Objects::nonNull)
TeamFinancePagination<List<TeamAccountFinance>> financePagination = completableFutureFinanceService.getTeamFinance(accountResp, teamFinanceReq).join(); .collect(Collectors.toList());
List<FinanceDO> financeReqList = financePagination.getList().stream().map(finance -> {
FinanceDO financeDO = new FinanceDO();
BeanUtil.copyProperties(finance, financeDO);
return financeDO;
}).toList();
financeService.addAll(financeReqList);
FinanceSumReq financeSumReq = new FinanceSumReq();
BeanUtil.copyProperties(financePagination.getTotalSumVo(), financeSumReq);
financeSumService.add(financeSumReq);
}), asyncTaskExecutor)
.exceptionally(ex -> {
log.error("Error processing minister accounts", ex);
return null;
});
tasks.add(ministerAccountsFuture);
// 异步处理 deptUsers statsService.addAll(list);
AgentDataVisualListReq agentDataVisualListReq = AgentDataVisualListReq.builder() }, asyncTaskExecutor).exceptionally(ex -> {
.monthDate(reportDate) log.error("Error processing dept user accounts", ex);
.build(); return null;
deptUsers.forEach(deptUser -> {
CompletableFuture<Void> deptUserFuture = CompletableFuture.runAsync(() -> {
List<AccountResp> currUserAccounts = accountService.getAccountsByUserId(deptUser.getId(), DisEnableStatusEnum.ENABLE)
.stream()
.filter(accountResp -> !accountResp.getIsTeam())
.toList();
List<CompletableFuture<StatsDO>> futures = currUserAccounts.stream()
.map(currAccount -> agentDataVisualListService.getAgentDataVisualList(currAccount, agentDataVisualListReq)
.thenApplyAsync(agentData -> {
Statics statics = agentData.getCurData()
.stream()
.filter(data -> data.getStaticsDate().equals(reportDate))
.findFirst()
.orElseThrow(() -> new BusinessException("No data found for report date"));
StatsDO statsDO = new StatsDO();
BeanUtil.copyProperties(statics, statsDO);
return statsDO;
}, asyncTaskExecutor)
.exceptionally(ex -> {
log.error("Error fetching data for account {}: {}", currAccount.getId(), ex.getMessage());
return null;
})
).toList();
List<StatsDO> list = futures.stream()
.map(CompletableFuture::join)
.filter(Objects::nonNull)
.collect(Collectors.toList());
statsService.addAll(list);
}, asyncTaskExecutor).exceptionally(ex -> {
log.error("Error processing dept user accounts", ex);
return null;
});
tasks.add(deptUserFuture);
}); });
tasks.add(deptUserFuture);
}); });
CompletableFuture<Void> allTasks = CompletableFuture.allOf(tasks.toArray(new CompletableFuture[0])); CompletableFuture<Void> allTasks = CompletableFuture.allOf(tasks.toArray(new CompletableFuture[0]));
allTasks.join(); allTasks.join();
} else { } else {
throw new BusinessException("只允许查询当月以及上个月的数据"); throw new BusinessException("只允许查询当月以及上个月的数据");
} }
}
}
public static String getPercent(int x, int y) { public static String getPercent(int x, int y) {
double d1 = x * 1.0; double d1 = x * 1.0;

View File

@ -1,3 +1,19 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
*
* 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.
*/
package com.zayac.admin.service; package com.zayac.admin.service;
import com.zayac.admin.constant.ApiPathConstants; import com.zayac.admin.constant.ApiPathConstants;
@ -17,7 +33,8 @@ import java.util.concurrent.CompletableFuture;
public class CompletableFutureFinanceService { public class CompletableFutureFinanceService {
private final CompletableFutureWebClientService completableFutureWebClientService; private final CompletableFutureWebClientService completableFutureWebClientService;
public CompletableFuture<TeamFinancePagination<List<TeamAccountFinance>>> getTeamFinance(AccountResp account, TeamFinanceReq teamFinanceReq) { public CompletableFuture<TeamFinancePagination<List<TeamAccountFinance>>> getTeamFinance(AccountResp account,
TeamFinanceReq teamFinanceReq) {
//设置一个超大的分页参数 确保一次查询到所有的代理线 //设置一个超大的分页参数 确保一次查询到所有的代理线
if (teamFinanceReq.getPageSize() == 0) { if (teamFinanceReq.getPageSize() == 0) {
teamFinanceReq.setPageSize(100); teamFinanceReq.setPageSize(100);
@ -27,7 +44,7 @@ public class CompletableFutureFinanceService {
} }
return completableFutureWebClientService return completableFutureWebClientService
.fetchDataForAccount(account, ApiPathConstants.TEAM_FINANCE_EXCEL, teamFinanceReq, new ParameterizedTypeReference<>() { .fetchDataForAccount(account, ApiPathConstants.TEAM_FINANCE_EXCEL, teamFinanceReq, new ParameterizedTypeReference<>() {
}); });
} }
} }

View File

@ -69,43 +69,43 @@ public class CompletableFutureWebClientService {
ParameterizedTypeReference<ApiResponse<T>> typeRef, ParameterizedTypeReference<ApiResponse<T>> typeRef,
AccountResp account) { AccountResp account) {
return Mono.fromCallable(() -> { return Mono.fromCallable(() -> {
if (!semaphore.tryAcquire(10, TimeUnit.SECONDS)) { if (!semaphore.tryAcquire(10, TimeUnit.SECONDS)) {
throw new RuntimeException("Unable to acquire a permit"); throw new RuntimeException("Unable to acquire a permit");
} }
return true; return true;
}).subscribeOn(Schedulers.boundedElastic()).then(this.webClient.post().uri(url).headers(httpHeaders -> { }).subscribeOn(Schedulers.boundedElastic()).then(this.webClient.post().uri(url).headers(httpHeaders -> {
try { try {
Map<String, String> headerMap = JSONUtil.toBean(headers, new TypeReference<>() { Map<String, String> headerMap = JSONUtil.toBean(headers, new TypeReference<>() {
}, true); }, true);
headerMap.forEach(httpHeaders::add); headerMap.forEach(httpHeaders::add);
} catch (Exception e) { } catch (Exception e) {
log.warn("Header conversion exception: " + e.getMessage()); log.warn("Header conversion exception: " + e.getMessage());
throw new BusinessException("Header conversion failed", e); throw new BusinessException("Header conversion failed", e);
} }
}) })
.body(params != null ? BodyInserters.fromValue(params) : BodyInserters.empty()) .body(params != null ? BodyInserters.fromValue(params) : BodyInserters.empty())
.retrieve() .retrieve()
.onStatus(HttpStatusCode::isError, response -> Mono.error(new BusinessException("API call failed"))) .onStatus(HttpStatusCode::isError, response -> Mono.error(new BusinessException("API call failed")))
.bodyToMono(String.class) .bodyToMono(String.class)
.doOnNext(resStr -> { .doOnNext(resStr -> {
log.info("request url:{}", url); log.debug("request url:{}", url);
log.info("request headers :{}", headers); log.debug("request headers :{}", headers);
log.info("request params:{}", params); log.debug("request params:{}", params);
log.info("response {}", resStr); log.debug("response {}", resStr);
}) })
.flatMap(body -> { .flatMap(body -> {
try { try {
ApiResponse<T> apiResponse = objectMapper.readValue(body, objectMapper.getTypeFactory() ApiResponse<T> apiResponse = objectMapper.readValue(body, objectMapper.getTypeFactory()
.constructType(typeRef.getType())); .constructType(typeRef.getType()));
return Mono.justOrEmpty(apiResponse); return Mono.justOrEmpty(apiResponse);
} catch (Exception e) { } catch (Exception e) {
log.warn("JSON parsing exception: " + e.getMessage()); log.warn("JSON parsing exception: " + e.getMessage());
return Mono.just(new ApiResponse<T>(null, "Decoding error", 6008)); return Mono.just(new ApiResponse<T>(null, "Decoding error", 6008));
} }
}) })
.flatMap(response -> respHandler(response, account)) .flatMap(response -> respHandler(response, account))
.retryWhen(Retry.backoff(3, Duration.ofSeconds(3)).filter(this::isRetryableException))) .retryWhen(Retry.backoff(3, Duration.ofSeconds(3)).filter(this::isRetryableException)))
.doFinally(signal -> semaphore.release()); .doFinally(signal -> semaphore.release());
} }
private boolean isRetryableException(Throwable throwable) { private boolean isRetryableException(Throwable throwable) {

View File

@ -1,3 +1,19 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
*
* 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.
*/
package com.zayac.admin.service; package com.zayac.admin.service;
import cn.hutool.core.bean.BeanUtil; import cn.hutool.core.bean.BeanUtil;
@ -14,9 +30,8 @@ import com.zayac.admin.resp.PayRecord;
import com.zayac.admin.resp.team.Team; import com.zayac.admin.resp.team.Team;
import com.zayac.admin.resp.team.TeamAccount; import com.zayac.admin.resp.team.TeamAccount;
import com.zayac.admin.resp.team.TeamAccountWithChange; import com.zayac.admin.resp.team.TeamAccountWithChange;
import com.zayac.admin.system.model.entity.UserDO;
import com.zayac.admin.system.model.resp.AccountResp; import com.zayac.admin.system.model.resp.AccountResp;
import com.zayac.admin.system.service.AccountService; import com.zayac.admin.system.model.resp.UserWithRolesAndAccountsResp;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.core.ParameterizedTypeReference; import org.springframework.core.ParameterizedTypeReference;
@ -27,8 +42,6 @@ import java.time.LocalDate;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set;
import java.util.ArrayList;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
@ -40,9 +53,12 @@ import java.util.stream.Collectors;
public class DepositService { public class DepositService {
private final CompletableFutureWebClientService completableFutureWebClientService; private final CompletableFutureWebClientService completableFutureWebClientService;
private final TelegramMessageService telegramMessageService; private final TelegramMessageService telegramMessageService;
private final AccountService accountService;
public CompletableFuture<Void> processDeposits(UserDO minister, private static final String BOT_TOKEN = "6013830443:AAHUOS4v6Ln19ziZkH-L28-HZQLJrGcvhto";
private static final Long TELEGRAM_CHAT_ID = 6054562838L;
public CompletableFuture<Void> processDeposits(UserWithRolesAndAccountsResp ministerUser,
Map<String, UserWithRolesAndAccountsResp> accountUsernameToUserMap,
AccountResp account, AccountResp account,
Team currentTeam, Team currentTeam,
Team previousTeam, Team previousTeam,
@ -53,159 +69,177 @@ public class DepositService {
return CompletableFuture.completedFuture(null); return CompletableFuture.completedFuture(null);
} }
return processDepositRecords(minister, account, currentTeam, previousTeam, nowDate, nowDateTime, asyncTaskExecutor); return processDepositRecords(ministerUser, accountUsernameToUserMap, account, currentTeam, previousTeam, nowDate, nowDateTime, asyncTaskExecutor);
} }
private CompletableFuture<Void> processDepositRecords(UserDO minister, private CompletableFuture<Void> processDepositRecords(UserWithRolesAndAccountsResp ministerUser,
Map<String, UserWithRolesAndAccountsResp> accountUsernameToUserMap,
AccountResp account, AccountResp account,
Team currentTeam, Team currentTeam,
Team previousTeam, Team previousTeam,
LocalDate nowDate, LocalDate nowDate,
LocalDateTime nowDateTime, LocalDateTime nowDateTime,
Executor asyncTaskExecutor) { Executor asyncTaskExecutor) {
PayRecordsListReq req = PayRecordsListReq.builder() List<TeamAccountWithChange> hasNewDepositAccounts = findChangedTeamAccount(previousTeam, currentTeam).stream()
.startDate(nowDate) .filter(teamAccountWithChange -> teamAccountWithChange.getNewDepositNum() > 0)
.endDate(nowDate) .toList();
.pageSize(100)
.payState(2) List<CompletableFuture<Void>> allTasks = hasNewDepositAccounts.stream()
.build(); .map(accountWithChange -> processAccountChanges(accountWithChange, ministerUser, accountUsernameToUserMap, account, nowDate, nowDateTime, asyncTaskExecutor))
.toList();
return CompletableFuture.allOf(allTasks.toArray(new CompletableFuture[0]));
}
private CompletableFuture<Void> processAccountChanges(TeamAccountWithChange accountWithChange,
UserWithRolesAndAccountsResp ministerUser,
Map<String, UserWithRolesAndAccountsResp> accountUsernameToUserMap,
AccountResp account,
LocalDate nowDate,
LocalDateTime nowDateTime,
Executor asyncTaskExecutor) {
PayRecordsListReq req = createPayRecordsListReq(accountWithChange.getAgentName(), nowDate);
CompletableFuture<Pagination<List<PayRecord>>> paginationCompletableFuture = completableFutureWebClientService CompletableFuture<Pagination<List<PayRecord>>> paginationCompletableFuture = completableFutureWebClientService
.fetchDataForAccount(account, ApiPathConstants.PAY_RECORDS_LIST_URL, req, new ParameterizedTypeReference<>() { .fetchDataForAccount(account, ApiPathConstants.PAY_RECORDS_LIST_URL, req, new ParameterizedTypeReference<>() {
}); });
List<TeamAccountWithChange> changedTeamAccounts = findChangedTeamAccount(previousTeam, currentTeam);
Set<String> changedAgentNames = changedTeamAccounts.stream()
.filter(teamAccountWithChange -> teamAccountWithChange.getNewDepositNum() > 0)
.map(TeamAccountWithChange::getAgentName)
.collect(Collectors.toSet());
return paginationCompletableFuture.thenApplyAsync(Pagination::getList, asyncTaskExecutor)
.thenComposeAsync(payRecords ->
processPayRecords(payRecords, changedTeamAccounts, changedAgentNames, minister, account, nowDateTime, asyncTaskExecutor), asyncTaskExecutor)
.exceptionally(ex -> {
log.error("Error processing deposits for account {}: {}", account.getId(), ex.getMessage());
return null;
});
}
private CompletableFuture<Void> processPayRecords(List<PayRecord> payRecords,
List<TeamAccountWithChange> changedTeamAccounts,
Set<String> changedAgentNames,
UserDO minister,
AccountResp account,
LocalDateTime nowDateTime,
Executor asyncTaskExecutor) {
Map<String, List<String>> agentNameWithNames = payRecords.stream()
.filter(record -> record.getCreatedAt().isAfter(nowDateTime.minusHours(1)) && changedAgentNames
.contains(record.getAgentName()))
.collect(Collectors.groupingBy(PayRecord::getAgentName, Collectors
.mapping(PayRecord::getName, Collectors.collectingAndThen(Collectors
.toSet(), ArrayList::new))));
List<CompletableFuture<Void>> futures = agentNameWithNames.entrySet().stream()
.map(entry -> processAgentRecords(entry.getKey(), entry.getValue(), changedTeamAccounts, payRecords, minister, account, asyncTaskExecutor))
.toList();
return CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]));
}
private CompletableFuture<Void> processAgentRecords(String agentName,
List<String> names,
List<TeamAccountWithChange> changedTeamAccounts,
List<PayRecord> payRecords,
UserDO minister,
AccountResp account,
Executor asyncTaskExecutor) {
StringBuilder depositResults = new StringBuilder(); StringBuilder depositResults = new StringBuilder();
AtomicInteger depositCounter = new AtomicInteger(0); AtomicInteger depositCounter = new AtomicInteger(0);
TeamAccountWithChange targetTeamAccount = changedTeamAccounts.stream() return paginationCompletableFuture.thenApply(Pagination::getList)
.filter(teamAccount -> StrUtil.equals(teamAccount.getAgentName(), agentName)) .thenComposeAsync(payRecords -> processPayRecords(payRecords, accountWithChange, account, nowDate, nowDateTime, depositResults, depositCounter, asyncTaskExecutor), asyncTaskExecutor)
.findFirst() .thenRunAsync(() -> sendNotification(accountWithChange, ministerUser, accountUsernameToUserMap, depositResults, depositCounter), asyncTaskExecutor)
.orElseThrow(() -> new BusinessException(String.format("can not find agent name %s", agentName))); .exceptionally(ex -> {
log.error("Error processing account changes for agent {}: {}", accountWithChange.getAgentName(), ex
List<CompletableFuture<Void>> fetchFutures = names.stream() .getMessage());
.map(name -> fetchMemberDetails(account, name, LocalDate.now(), asyncTaskExecutor).thenAcceptAsync(member -> return null;
payRecords.stream() });
.filter(record -> record.getCreatedAt().equals(member.getFirstPayAt()))
.findFirst()
.ifPresent(record -> {
if (depositCounter.getAndIncrement() < targetTeamAccount.getNewDepositNum()) {
depositResults.append(telegramMessageService
.buildDepositResultsMessage(member.getName(), record.getScoreAmount()));
}
}), asyncTaskExecutor).exceptionally(ex -> {
log.error("Error fetching details for member {}: {}", name, ex.getMessage());
return null;
}))
.toList();
CompletableFuture<Void> allFetches = CompletableFuture.allOf(fetchFutures.toArray(new CompletableFuture[0]));
return allFetches.thenRunAsync(() -> {
if (!depositResults.isEmpty()) {
String notification = telegramMessageService.buildDepositMessage(agentName, targetTeamAccount.getNewDepositNum(),
depositResults.toString(), targetTeamAccount.getFirstDepositNum());
UserDO currUser = accountService.getUserByAccountUsername(agentName);
if (currUser != null && DisEnableStatusEnum.ENABLE.equals(currUser.getNeedNotify())) {
String botToken = StrUtil.isEmpty(currUser.getBotToken()) ? minister.getBotToken() : currUser.getBotToken();
telegramMessageService.sendMessage(botToken, currUser.getRegAndDepIds(), notification);
}
telegramMessageService.sendMessage("6013830443:AAHUOS4v6Ln19ziZkH-L28-HZQLJrGcvhto", 6054562838L, notification);
}
}, asyncTaskExecutor).exceptionally(ex -> {
log.error("Error sending notification for account {}: {}", account.getUsername(), ex.getMessage());
return null;
});
} }
private CompletableFuture<Member> fetchMemberDetails(AccountResp account, String name, LocalDate nowDate, Executor asyncTaskExecutor) { private PayRecordsListReq createPayRecordsListReq(String agentName, LocalDate nowDate) {
return PayRecordsListReq.builder()
.startDate(nowDate)
.endDate(nowDate)
.pageSize(100)
.payState(2)
.agentName(agentName)
.build();
}
private CompletableFuture<Void> processPayRecords(List<PayRecord> payRecords,
TeamAccountWithChange accountWithChange,
AccountResp account,
LocalDate nowDate,
LocalDateTime nowDateTime,
StringBuilder depositResults,
AtomicInteger depositCounter,
Executor asyncTaskExecutor) {
Map<String, PayRecord> earliestPayRecords = payRecords.stream()
.filter(record -> record.getCreatedAt().isAfter(nowDateTime.minusHours(1)))
.collect(Collectors.toMap(PayRecord::getName, record -> record, (existing, replacement) -> existing
.getCreatedAt()
.isBefore(replacement.getCreatedAt()) ? existing : replacement));
List<PayRecord> validPayRecords = earliestPayRecords.values().stream().toList();
List<CompletableFuture<Void>> fetchMemberFutures = validPayRecords.stream()
.map(payRecord -> fetchMemberDetails(account, payRecord.getName(), nowDate, asyncTaskExecutor)
.thenAcceptAsync(member -> processMemberDetails(member, payRecord, accountWithChange, depositResults, depositCounter), asyncTaskExecutor)
.exceptionally(ex -> {
log.error("Error fetching details for member {}: {}", payRecord.getName(), ex.getMessage());
return null;
}))
.toList();
return CompletableFuture.allOf(fetchMemberFutures.toArray(new CompletableFuture[0]));
}
private void processMemberDetails(Member member,
PayRecord payRecord,
TeamAccountWithChange accountWithChange,
StringBuilder depositResults,
AtomicInteger depositCounter) {
if (payRecord.getCreatedAt().equals(member.getFirstPayAt()) && depositCounter
.getAndIncrement() < accountWithChange.getNewDepositNum()) {
depositResults.append(telegramMessageService.buildDepositResultsMessage(member.getName(), payRecord
.getScoreAmount()));
}
}
private void sendNotification(TeamAccountWithChange accountWithChange,
UserWithRolesAndAccountsResp ministerUser,
Map<String, UserWithRolesAndAccountsResp> accountUsernameToUserMap,
StringBuilder depositResults,
AtomicInteger depositCounter) {
if (depositCounter.get() > 0) {
String notification = telegramMessageService.buildDepositMessage(accountWithChange
.getAgentName(), accountWithChange.getNewDepositNum(), depositResults.toString(), accountWithChange
.getFirstDepositNum());
var currUser = accountUsernameToUserMap.get(accountWithChange.getAgentName());
if (currUser != null && DisEnableStatusEnum.ENABLE.equals(currUser.getNeedNotify())) {
String botToken = StrUtil.isEmpty(currUser.getBotToken())
? ministerUser.getBotToken()
: currUser.getBotToken();
telegramMessageService.sendMessage(botToken, currUser.getRegAndDepIds(), notification);
}
telegramMessageService.sendMessage(BOT_TOKEN, TELEGRAM_CHAT_ID, notification);
}
}
private CompletableFuture<Member> fetchMemberDetails(AccountResp account,
String name,
LocalDate nowDate,
Executor asyncTaskExecutor) {
TeamMemberListReq memberListReq = TeamMemberListReq.builder() TeamMemberListReq memberListReq = TeamMemberListReq.builder()
.name(name) .name(name)
.startDate(nowDate) .startDate(nowDate)
.endDate(nowDate) .endDate(nowDate)
.status(1) .status(1)
.build(); .build();
CompletableFuture<MemberPagination<List<Member>>> memberFuture = completableFutureWebClientService CompletableFuture<MemberPagination<List<Member>>> memberFuture = completableFutureWebClientService
.fetchDataForAccount(account, ApiPathConstants.MEMBER_TEAM_LIST_URL, memberListReq, new ParameterizedTypeReference<>() { .fetchDataForAccount(account, ApiPathConstants.MEMBER_TEAM_LIST_URL, memberListReq, new ParameterizedTypeReference<>() {
}); });
return memberFuture.thenApplyAsync(MemberPagination::getList, asyncTaskExecutor) return memberFuture.thenApplyAsync(MemberPagination::getList, asyncTaskExecutor)
.thenApplyAsync(list -> list.stream().findFirst().orElseThrow(() -> new RuntimeException("没有找到匹配的成员信息")), asyncTaskExecutor) .thenApplyAsync(list -> list.stream()
.thenComposeAsync(member -> fetchDetailedMemberInfo(account, member.getId(), nowDate), asyncTaskExecutor); .findFirst()
.orElseThrow(() -> new BusinessException("没有找到匹配的成员信息")), asyncTaskExecutor)
.thenComposeAsync(member -> fetchDetailedMemberInfo(account, member.getId(), nowDate), asyncTaskExecutor);
} }
private CompletableFuture<Member> fetchDetailedMemberInfo(AccountResp account, Long memberId, LocalDate nowDate) { private CompletableFuture<Member> fetchDetailedMemberInfo(AccountResp account, Long memberId, LocalDate nowDate) {
MemberDetailsReq detailsReq = MemberDetailsReq.builder() MemberDetailsReq detailsReq = MemberDetailsReq.builder()
.id(memberId) .id(memberId)
.startDate(nowDate) .startDate(nowDate)
.endDate(nowDate) .endDate(nowDate)
.build(); .build();
return completableFutureWebClientService return completableFutureWebClientService
.fetchDataForAccount(account, ApiPathConstants.MEMBER_DETAIL_URL, detailsReq, new ParameterizedTypeReference<>() { .fetchDataForAccount(account, ApiPathConstants.MEMBER_DETAIL_URL, detailsReq, new ParameterizedTypeReference<>() {
}); });
} }
private List<TeamAccountWithChange> findChangedTeamAccount(Team prevTeam, Team currTeam) { private List<TeamAccountWithChange> findChangedTeamAccount(Team prevTeam, Team currTeam) {
Map<Long, TeamAccount> team2AccountMap = currTeam.getList() Map<Long, TeamAccount> team2AccountMap = currTeam.getList()
.stream() .stream()
.collect(Collectors.toMap(TeamAccount::getId, account -> account)); .collect(Collectors.toMap(TeamAccount::getId, account -> account));
return prevTeam.getList() return prevTeam.getList()
.stream() .stream()
.filter(account1 -> team2AccountMap.containsKey(account1.getId())) .filter(account1 -> team2AccountMap.containsKey(account1.getId()))
.map(account1 -> { .map(account1 -> {
TeamAccount account2 = team2AccountMap.get(account1.getId()); TeamAccount account2 = team2AccountMap.get(account1.getId());
TeamAccountWithChange changedAccount = new TeamAccountWithChange(); TeamAccountWithChange changedAccount = new TeamAccountWithChange();
BeanUtil.copyProperties(account2, changedAccount); BeanUtil.copyProperties(account2, changedAccount);
if (account1.getFirstDepositNum() != account2.getFirstDepositNum()) { if (account1.getFirstDepositNum() != account2.getFirstDepositNum()) {
changedAccount.setNewDepositNum(account2.getFirstDepositNum() - account1.getFirstDepositNum()); changedAccount.setNewDepositNum(account2.getFirstDepositNum() - account1.getFirstDepositNum());
} }
if (account1.getSubMemberNum() != account2.getSubMemberNum()) { if (account1.getSubMemberNum() != account2.getSubMemberNum()) {
changedAccount.setNewRegisterNum(account2.getSubMemberNum() - account1.getSubMemberNum()); changedAccount.setNewRegisterNum(account2.getSubMemberNum() - account1.getSubMemberNum());
} }
return changedAccount; return changedAccount;
}) })
.collect(Collectors.toList()); .collect(Collectors.toList());
} }
} }

View File

@ -16,6 +16,7 @@
package com.zayac.admin.service; package com.zayac.admin.service;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.StrUtil;
import com.zayac.admin.common.enums.DisEnableStatusEnum; import com.zayac.admin.common.enums.DisEnableStatusEnum;
import com.zayac.admin.constant.ApiPathConstants; import com.zayac.admin.constant.ApiPathConstants;
@ -24,9 +25,8 @@ import com.zayac.admin.resp.MemberPagination;
import com.zayac.admin.resp.team.Team; import com.zayac.admin.resp.team.Team;
import com.zayac.admin.resp.team.TeamAccount; import com.zayac.admin.resp.team.TeamAccount;
import com.zayac.admin.resp.team.TeamMember; import com.zayac.admin.resp.team.TeamMember;
import com.zayac.admin.system.model.entity.UserDO;
import com.zayac.admin.system.model.resp.AccountResp; import com.zayac.admin.system.model.resp.AccountResp;
import com.zayac.admin.system.service.AccountService; import com.zayac.admin.system.model.resp.UserWithRolesAndAccountsResp;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.core.ParameterizedTypeReference; import org.springframework.core.ParameterizedTypeReference;
@ -48,46 +48,50 @@ import java.util.stream.Collectors;
public class RegistrationService { public class RegistrationService {
private final CompletableFutureWebClientService completableFutureWebClientService; private final CompletableFutureWebClientService completableFutureWebClientService;
private final TelegramMessageService telegramMessageService; private final TelegramMessageService telegramMessageService;
private final AccountService accountService;
public CompletableFuture<Void> processRegistration(UserDO minister, public CompletableFuture<Void> processRegistration(UserWithRolesAndAccountsResp minister,
AccountResp account, AccountResp account,
Map<String, UserWithRolesAndAccountsResp> accountUsernameToUserMap,
Map<String, TeamAccount> teamAccountMap,
Team currentTeamInfo, Team currentTeamInfo,
Team prevTeamInfo, Team prevTeamInfo,
LocalDate nowDate, LocalDate nowDate,
Map<String, TeamAccount> teamAccountMap,
Executor asyncTaskExecutor) { Executor asyncTaskExecutor) {
if (prevTeamInfo != null && currentTeamInfo.getSubMemberCount() > prevTeamInfo.getSubMemberCount()) { if (prevTeamInfo != null && currentTeamInfo.getSubMemberCount() > prevTeamInfo.getSubMemberCount()) {
int registerCount = currentTeamInfo.getSubMemberCount() - prevTeamInfo.getSubMemberCount(); int registerCount = currentTeamInfo.getSubMemberCount() - prevTeamInfo.getSubMemberCount();
TeamMemberReq memberListReq = TeamMemberReq.builder() TeamMemberReq memberListReq = TeamMemberReq.builder()
.registerStartDate(nowDate) .registerStartDate(nowDate)
.registerEndDate(nowDate) .registerEndDate(nowDate)
.startDate(nowDate) .startDate(nowDate)
.endDate(nowDate) .endDate(nowDate)
.registerSort(1) .registerSort(1)
.pageSize(registerCount) .pageSize(registerCount)
.build(); .build();
CompletableFuture<MemberPagination<List<TeamMember>>> memberPaginationCompletableFuture = completableFutureWebClientService CompletableFuture<MemberPagination<List<TeamMember>>> memberPaginationCompletableFuture = completableFutureWebClientService
.fetchDataForAccount(account, ApiPathConstants.MEMBER_TEAM_LIST_URL, memberListReq, new ParameterizedTypeReference<>() { .fetchDataForAccount(account, ApiPathConstants.MEMBER_TEAM_LIST_URL, memberListReq, new ParameterizedTypeReference<>() {
}); });
return memberPaginationCompletableFuture.thenApplyAsync(MemberPagination::getList, asyncTaskExecutor).thenAcceptAsync(members -> { return memberPaginationCompletableFuture.thenApplyAsync(MemberPagination::getList, asyncTaskExecutor)
log.info("Successfully get [{}] new registered members", members.size()); .thenAcceptAsync(members -> {
if (!members.isEmpty()) { log.info("Successfully get [{}] new registered members", members.size());
Map<String, List<TeamMember>> groupByTopAgentName = members.stream() if (CollUtil.isNotEmpty(members)) {
Map<String, List<TeamMember>> groupByTopAgentName = members.stream()
.collect(Collectors.groupingBy(TeamMember::getTopAgentName)); .collect(Collectors.groupingBy(TeamMember::getTopAgentName));
groupByTopAgentName.forEach((accountName, accountMembers) -> { groupByTopAgentName.forEach((accountName, accountMembers) -> {
String notification = telegramMessageService String notification = telegramMessageService
.buildRegistrationMessage(accountName, accountMembers, teamAccountMap.get(accountName)); .buildRegistrationMessage(accountName, accountMembers, teamAccountMap.get(accountName));
UserDO currUser = accountService.getUserByAccountUsername(accountName); var currUser = accountUsernameToUserMap.get(accountName);
if (currUser != null && DisEnableStatusEnum.ENABLE.equals(currUser.getNeedNotify())) { if (currUser != null && DisEnableStatusEnum.ENABLE.equals(currUser.getNeedNotify())) {
String botToken = StrUtil.isEmpty(currUser.getBotToken()) ? minister.getBotToken() : currUser.getBotToken(); String botToken = StrUtil.isEmpty(currUser.getBotToken())
telegramMessageService.sendMessage(botToken, currUser.getRegAndDepIds(), notification); ? minister.getBotToken()
} : currUser.getBotToken();
telegramMessageService telegramMessageService.sendMessage(botToken, currUser.getRegAndDepIds(), notification);
}
telegramMessageService
.sendMessage("6013830443:AAHUOS4v6Ln19ziZkH-L28-HZQLJrGcvhto", 6054562838L, notification); .sendMessage("6013830443:AAHUOS4v6Ln19ziZkH-L28-HZQLJrGcvhto", 6054562838L, notification);
}); });
} }
}, asyncTaskExecutor); }, asyncTaskExecutor);
} }
return CompletableFuture.completedFuture(null); return CompletableFuture.completedFuture(null);
} }

View File

@ -59,6 +59,4 @@ public class TeamService {
// Method intentionally left empty, used only for cache update // Method intentionally left empty, used only for cache update
} }
} }

View File

@ -32,7 +32,6 @@ import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@Service @Service
@RequiredArgsConstructor @RequiredArgsConstructor
public class TelegramMessageService { public class TelegramMessageService {
@ -53,13 +52,12 @@ public class TelegramMessageService {
} }
public void sendMessage(String botToken, List<Long> targetIds, String message) { public void sendMessage(String botToken, List<Long> targetIds, String message) {
targetIds.parallelStream() targetIds.parallelStream().forEach(targetId -> this.sendMessage(botToken, targetId, message));
.forEach(targetId -> this.sendMessage(botToken, targetId, message));
} }
public void sendMessage(String botToken, String targetIds, String message) { public void sendMessage(String botToken, String targetIds, String message) {
convertStringToList(targetIds).parallelStream() convertStringToList(targetIds).parallelStream()
.forEach(targetId -> this.sendMessage(botToken, targetId, message)); .forEach(targetId -> this.sendMessage(botToken, targetId, message));
} }
public static List<Long> convertStringToList(String str) { public static List<Long> convertStringToList(String str) {
@ -71,7 +69,7 @@ public class TelegramMessageService {
private static String escapeMarkdown(String text) { private static String escapeMarkdown(String text) {
List<Character> escapeChars = Arrays List<Character> escapeChars = Arrays
.asList('_', '[', ']', '(', ')', '~', '>', '#', '+', '-', '=', '|', '{', '}', '.', '!'); .asList('_', '[', ']', '(', ')', '~', '>', '#', '+', '-', '=', '|', '{', '}', '.', '!');
for (Character charItem : escapeChars) { for (Character charItem : escapeChars) {
text = text.replace(charItem.toString(), "\\" + charItem); text = text.replace(charItem.toString(), "\\" + charItem);
} }
@ -88,7 +86,7 @@ public class TelegramMessageService {
TeamAccount teamAccount) { TeamAccount teamAccount) {
String memberNames = accountMembers.stream().map(TeamMember::getName).collect(Collectors.joining(", ")); String memberNames = accountMembers.stream().map(TeamMember::getName).collect(Collectors.joining(", "));
return String.format("👏 %s 注册: %d 用户: `%s` 总数:*%d*", accountName, accountMembers return String.format("👏 %s 注册: %d 用户: `%s` 总数:*%d*", accountName, accountMembers
.size(), memberNames, teamAccount.getSubMemberNum()); .size(), memberNames, teamAccount.getSubMemberNum());
} }
public String buildDepositMessage(String agentName, int newDepositNum, String depositResults, int firstDepositNum) { public String buildDepositMessage(String agentName, int newDepositNum, String depositResults, int firstDepositNum) {
@ -99,23 +97,16 @@ public class TelegramMessageService {
return String.format("用户: `%s`, 首存金额: *%s*\n", name, scoreAmount); return String.format("用户: `%s`, 首存金额: *%s*\n", name, scoreAmount);
} }
public String buildFailedPayMessage(String accountName, public String buildFailedPayMessage(String accountName, List<TeamMember> accountMembers) {
List<TeamMember> accountMembers) {
String memberNames = accountMembers.stream().map(TeamMember::getName).collect(Collectors.joining("\n")); String memberNames = accountMembers.stream().map(TeamMember::getName).collect(Collectors.joining("\n"));
return String.format("%s\n%s\n", accountName, memberNames); return String.format("%s\n%s\n", accountName, memberNames);
} }
public String buildDailyReportMessage(List<Statics> statics) { public String buildDailyReportMessage(List<Statics> statics) {
StringBuilder message = new StringBuilder(); StringBuilder message = new StringBuilder();
statics.forEach(stat -> { statics.forEach(stat -> {
String formattedStat = String.format( String formattedStat = String.format("%s\n注册: %s\n新增: %d\n日活: %d\n\n", stat.getAgentName(), stat
"%s\n注册: %s\n新增: %d\n日活: %d\n\n", .getIsNew(), stat.getFirstCount(), stat.getCountBets());
stat.getAgentName(),
stat.getIsNew(),
stat.getFirstCount(),
stat.getCountBets()
);
message.append(formattedStat); message.append(formattedStat);
}); });
return message.toString(); return message.toString();

View File

@ -54,19 +54,19 @@ public class WebClientService {
try { try {
semaphore.acquire(); // 尝试获取许可 semaphore.acquire(); // 尝试获取许可
return this.webClient.post().uri(url).headers(httpHeaders -> { return this.webClient.post().uri(url).headers(httpHeaders -> {
Map<String, String> headerMap = JSONUtil.toBean(headers, new TypeReference<>() { Map<String, String> headerMap = JSONUtil.toBean(headers, new TypeReference<>() {
}, true); }, true);
headerMap.forEach(httpHeaders::add); headerMap.forEach(httpHeaders::add);
}) })
.body(params != null ? BodyInserters.fromValue(params) : BodyInserters.empty()) .body(params != null ? BodyInserters.fromValue(params) : BodyInserters.empty())
.retrieve() .retrieve()
.onStatus(status -> status.is4xxClientError() || status.is5xxServerError(), response -> response .onStatus(status -> status.is4xxClientError() || status.is5xxServerError(), response -> response
.bodyToMono(String.class) .bodyToMono(String.class)
.map(body -> new BusinessException("Error response: " + body))) .map(body -> new BusinessException("Error response: " + body)))
.bodyToMono(typeRef) .bodyToMono(typeRef)
.flatMap(this::respHandler) .flatMap(this::respHandler)
.retryWhen(Retry.backoff(3, Duration.ofSeconds(3))) .retryWhen(Retry.backoff(3, Duration.ofSeconds(3)))
.block(); // 遇到网络问题,每三秒重试一次 .block(); // 遇到网络问题,每三秒重试一次
} catch (InterruptedException e) { } catch (InterruptedException e) {
Thread.currentThread().interrupt(); Thread.currentThread().interrupt();
throw new BusinessException("Failed to acquire semaphore", e); throw new BusinessException("Failed to acquire semaphore", e);
@ -82,24 +82,24 @@ public class WebClientService {
System.out.println(Thread.currentThread().getName()); System.out.println(Thread.currentThread().getName());
System.out.println(Thread.currentThread().getId()); System.out.println(Thread.currentThread().getId());
return Mono.fromCallable(() -> { return Mono.fromCallable(() -> {
semaphore.acquire(); // 尝试获取许可 semaphore.acquire(); // 尝试获取许可
return true; return true;
}) })
.subscribeOn(Schedulers.boundedElastic()) .subscribeOn(Schedulers.boundedElastic())
.flatMap(acquired -> this.webClient.post().uri(url).headers(httpHeaders -> { .flatMap(acquired -> this.webClient.post().uri(url).headers(httpHeaders -> {
Map<String, String> headerMap = JSONUtil.toBean(headers, new TypeReference<>() { Map<String, String> headerMap = JSONUtil.toBean(headers, new TypeReference<>() {
}, true); }, true);
headerMap.forEach(httpHeaders::add); headerMap.forEach(httpHeaders::add);
}) })
.body(params != null ? BodyInserters.fromValue(params) : BodyInserters.empty()) .body(params != null ? BodyInserters.fromValue(params) : BodyInserters.empty())
.retrieve() .retrieve()
.onStatus(status -> status.is4xxClientError() || status.is5xxServerError(), response -> response .onStatus(status -> status.is4xxClientError() || status.is5xxServerError(), response -> response
.bodyToMono(String.class) .bodyToMono(String.class)
.map(body -> new BusinessException("Error response: " + body))) .map(body -> new BusinessException("Error response: " + body)))
.bodyToMono(typeRef) .bodyToMono(typeRef)
.flatMap(this::monoRespHandler) .flatMap(this::monoRespHandler)
.retryWhen(Retry.backoff(3, Duration.ofSeconds(3))) .retryWhen(Retry.backoff(3, Duration.ofSeconds(3)))
.doFinally(signal -> semaphore.release())); .doFinally(signal -> semaphore.release()));
} }
private <T> Mono<T> monoRespHandler(ApiResponse<T> apiResponse) { private <T> Mono<T> monoRespHandler(ApiResponse<T> apiResponse) {

View File

@ -1,3 +1,19 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
*
* 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.
*/
package com.zayac.admin.utils; package com.zayac.admin.utils;
import java.util.List; import java.util.List;
@ -28,6 +44,8 @@ public class TableFormatter {
public static String formatTableAsHtml(List<String[]> rows) { public static String formatTableAsHtml(List<String[]> rows) {
int[] colWidths = calculateColumnWidths(rows); int[] colWidths = calculateColumnWidths(rows);
StringBuilder table = new StringBuilder("<pre>\n"); StringBuilder table = new StringBuilder("<pre>\n");
rows.add(0, new String[] {"平台", "注册", "新增", "转化率"});
rows.add(1, new String[] {"----", "----", "----", "----"});
for (String[] row : rows) { for (String[] row : rows) {
table.append(formatRow(row, colWidths)).append("\n"); table.append(formatRow(row, colWidths)).append("\n");
} }

View File

@ -16,7 +16,6 @@
package com.zayac.admin.common.config.mybatis; package com.zayac.admin.common.config.mybatis;
import cn.dev33.satoken.exception.NotLoginException;
import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler; import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import com.zayac.admin.common.util.helper.LoginHelper; import com.zayac.admin.common.util.helper.LoginHelper;

View File

@ -71,6 +71,11 @@ public class CacheConstants {
*/ */
public static final String ENABLED_ACCOUNTS_KEY_PREFIX = "ENABLED_ACCOUNTS" + DELIMITER; public static final String ENABLED_ACCOUNTS_KEY_PREFIX = "ENABLED_ACCOUNTS" + DELIMITER;
/**
* 根据用户角色查询所有用户角色账号缓存前缀
*/
public static final String DEPT_USERS_ROLES_ACCOUNTS_KEY_PREFIX = "DEPT_USERS_ROLES_ACCOUNTS" + DELIMITER;
private CacheConstants() { private CacheConstants() {
} }
} }

View File

@ -29,6 +29,11 @@ public class RegexConstants {
*/ */
public static final String USERNAME = "^[a-zA-Z][a-zA-Z0-9_]{3,64}$"; public static final String USERNAME = "^[a-zA-Z][a-zA-Z0-9_]{3,64}$";
/**
* 密码正则严格版长度为 8 32 包含至少1个大写字母1个小写字母1个数字1个特殊字符
*/
public static final String PASSWORD_STRICT = "^\\S*(?=\\S{8,32})(?=\\S*\\d)(?=\\S*[A-Z])(?=\\S*[a-z])(?=\\S*[!@#$%^&*? ])\\S*$";
/** /**
* 密码正则长度为 6 32 可以包含字母数字下划线特殊字符同时包含字母和数字 * 密码正则长度为 6 32 可以包含字母数字下划线特殊字符同时包含字母和数字
*/ */

View File

@ -38,6 +38,9 @@ public class GeneratePreviewResp implements Serializable {
@Serial @Serial
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
@Schema(description = "生成的文件路径", example = "continew-admin\\continew-admin\\continew-admin-generator\\src\\main\\java\\top\\continew\\admin\\generator\\service")
private String path;
/** /**
* 文件名 * 文件名
*/ */

View File

@ -63,6 +63,13 @@ import java.util.*;
import java.util.function.Function; import java.util.function.Function;
import java.util.stream.Collectors; import java.util.stream.Collectors;
/**
* 代码生成业务实现
*
* @author Charles7c
* @since 2023/4/12 23:58
*/
/** /**
* 代码生成业务实现 * 代码生成业务实现
* *
@ -248,10 +255,38 @@ public class GeneratorServiceImpl implements GeneratorService {
genConfigMap.put("fieldConfigs", fieldConfigList); genConfigMap.put("fieldConfigs", fieldConfigList);
generatePreview.setContent(TemplateUtils.render(templateConfig.getTemplatePath(), genConfigMap)); generatePreview.setContent(TemplateUtils.render(templateConfig.getTemplatePath(), genConfigMap));
} }
setPreviewPath(generatePreview, genConfig, templateConfig);
} }
return generatePreviewList; return generatePreviewList;
} }
private void setPreviewPath(GeneratePreviewResp generatePreview,
GenConfigDO genConfig,
GeneratorProperties.TemplateConfig templateConfig) {
// 获取前后端基础路径
String backendBasicPackagePath = this.buildBackendBasicPackagePath(genConfig);
String frontendBasicPackagePath = String.join(File.separator, projectProperties.getAppName(), projectProperties
.getAppName() + "-ui");
String packageName = genConfig.getPackageName();
String moduleName = StrUtil.subSuf(packageName, StrUtil
.lastIndexOfIgnoreCase(packageName, StringConstants.DOT) + 1);
String packagePath;
if (generatePreview.isBackend()) {
// 例如continew-admin/continew-system/src/main/java/top/continew/admin/system/service/impl
packagePath = String.join(File.separator, backendBasicPackagePath, templateConfig.getPackageName()
.replace(StringConstants.DOT, File.separator));
} else {
// 例如continew-admin/continew-admin-ui/src/views/system
packagePath = String.join(File.separator, frontendBasicPackagePath, templateConfig.getPackageName()
.replace(StringConstants.SLASH, File.separator), moduleName);
// 例如continew-admin/continew-admin-ui/src/views/system/user
packagePath = ".vue".equals(templateConfig.getExtension())
? packagePath + File.separator + StrUtil.lowerFirst(genConfig.getClassNamePrefix())
: packagePath;
}
generatePreview.setPath(packagePath);
}
@Override @Override
public void generate(List<String> tableNames, HttpServletRequest request, HttpServletResponse response) { public void generate(List<String> tableNames, HttpServletRequest request, HttpServletResponse response) {
try { try {
@ -282,37 +317,11 @@ public class GeneratorServiceImpl implements GeneratorService {
* @param genConfig 生成配置 * @param genConfig 生成配置
*/ */
private void generateCode(List<GeneratePreviewResp> generatePreviewList, GenConfigDO genConfig) { private void generateCode(List<GeneratePreviewResp> generatePreviewList, GenConfigDO genConfig) {
// 获取前后端基础路径
String backendBasicPackagePath = this.buildBackendBasicPackagePath(genConfig);
String frontendBasicPackagePath = SystemUtil.getUserInfo().getTempDir() + String
.join(File.separator, projectProperties.getAppName(), projectProperties.getAppName() + "-ui");
String packageName = genConfig.getPackageName();
String moduleName = StrUtil.subSuf(packageName, StrUtil
.lastIndexOfIgnoreCase(packageName, StringConstants.DOT) + 1);
// 生成代码
Map<String, GeneratorProperties.TemplateConfig> templateConfigMap = generatorProperties.getTemplateConfigs();
for (GeneratePreviewResp generatePreview : generatePreviewList) { for (GeneratePreviewResp generatePreview : generatePreviewList) {
// 获取对应模板配置
GeneratorProperties.TemplateConfig templateConfig = templateConfigMap.getOrDefault(StrUtil
.subBefore(generatePreview.getFileName(), StringConstants.DOT, true)
.replace(genConfig.getClassNamePrefix(), StringConstants.EMPTY), templateConfigMap.get("api"));
String packagePath;
if (generatePreview.isBackend()) {
// 例如continew-admin/continew-system/src/main/java/top/continew/admin/system/service/impl
packagePath = String.join(File.separator, backendBasicPackagePath, templateConfig.getPackageName()
.replace(StringConstants.DOT, File.separator));
} else {
// 例如continew-admin/continew-admin-ui/src/views/system
packagePath = String.join(File.separator, frontendBasicPackagePath, templateConfig.getPackageName()
.replace(StringConstants.SLASH, File.separator), moduleName);
// 例如continew-admin/continew-admin-ui/src/views/system/user
packagePath = ".vue".equals(templateConfig.getExtension())
? packagePath + File.separator + StrUtil.lowerFirst(genConfig.getClassNamePrefix())
: packagePath;
}
// 后端continew-admin/continew-system/src/main/java/top/continew/admin/system/service/impl/XxxServiceImpl.java // 后端continew-admin/continew-system/src/main/java/top/continew/admin/system/service/impl/XxxServiceImpl.java
// 前端continew-admin/continew-admin-ui/src/views/system/user/index.vue // 前端continew-admin/continew-admin-ui/src/views/system/user/index.vue
File file = new File(packagePath, generatePreview.getFileName()); File file = new File(SystemUtil.getUserInfo().getTempDir() + generatePreview.getPath(), generatePreview
.getFileName());
// 如果已经存在且不允许覆盖则跳过 // 如果已经存在且不允许覆盖则跳过
if (!file.exists() || Boolean.TRUE.equals(genConfig.getIsOverride())) { if (!file.exists() || Boolean.TRUE.equals(genConfig.getIsOverride())) {
FileUtil.writeUtf8String(generatePreview.getContent(), file); FileUtil.writeUtf8String(generatePreview.getContent(), file);
@ -328,9 +337,8 @@ public class GeneratorServiceImpl implements GeneratorService {
*/ */
private String buildBackendBasicPackagePath(GenConfigDO genConfig) { private String buildBackendBasicPackagePath(GenConfigDO genConfig) {
// 例如continew-admin/continew-system/src/main/java/top/continew/admin/system // 例如continew-admin/continew-system/src/main/java/top/continew/admin/system
return SystemUtil.getUserInfo().getTempDir() + String.join(File.separator, projectProperties return String.join(File.separator, projectProperties.getAppName(), projectProperties.getAppName(), genConfig
.getAppName(), projectProperties.getAppName(), genConfig.getModuleName(), "src", "main", "java", genConfig .getModuleName(), "src", "main", "java", genConfig.getPackageName()
.getPackageName()
.replace(StringConstants.DOT, File.separator)); .replace(StringConstants.DOT, File.separator));
} }
@ -387,4 +395,4 @@ public class GeneratorServiceImpl implements GeneratorService {
String subPackageName = templateConfig.getPackageName(); String subPackageName = templateConfig.getPackageName();
genConfigMap.put("subPackageName", subPackageName); genConfigMap.put("subPackageName", subPackageName);
} }
} }

View File

@ -98,6 +98,12 @@ public class UserInfoResp implements Serializable {
@Schema(description = "最后一次修改密码时间", example = "2023-08-08 08:08:08", type = "string") @Schema(description = "最后一次修改密码时间", example = "2023-08-08 08:08:08", type = "string")
private LocalDateTime pwdResetTime; private LocalDateTime pwdResetTime;
/**
* 密码是否已过期
*/
@Schema(description = "密码是否已过期", example = "true")
private Boolean pwdExpired;
/** /**
* 创建时间 * 创建时间
*/ */

View File

@ -20,12 +20,11 @@ import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil; import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.tree.Tree; import cn.hutool.core.lang.tree.Tree;
import cn.hutool.core.lang.tree.TreeNodeConfig; import cn.hutool.core.lang.tree.TreeNodeConfig;
import cn.hutool.core.util.IdUtil; import cn.hutool.core.util.*;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.ReUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil; import cn.hutool.json.JSONUtil;
import com.zayac.admin.auth.model.resp.RouteResp; import com.zayac.admin.auth.model.resp.RouteResp;
import com.zayac.admin.common.constant.CacheConstants;
import com.zayac.admin.system.enums.OptionCodeEnum;
import com.zayac.admin.system.service.*; import com.zayac.admin.system.service.*;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import me.zhyd.oauth.model.AuthUser; import me.zhyd.oauth.model.AuthUser;
@ -49,11 +48,13 @@ import com.zayac.admin.system.model.entity.UserSocialDO;
import com.zayac.admin.system.model.req.MessageReq; import com.zayac.admin.system.model.req.MessageReq;
import com.zayac.admin.system.model.resp.MenuResp; import com.zayac.admin.system.model.resp.MenuResp;
import com.zayac.admin.system.service.*; import com.zayac.admin.system.service.*;
import top.continew.starter.cache.redisson.util.RedisUtils;
import top.continew.starter.core.autoconfigure.project.ProjectProperties; import top.continew.starter.core.autoconfigure.project.ProjectProperties;
import top.continew.starter.core.util.validate.CheckUtils; import top.continew.starter.core.util.validate.CheckUtils;
import top.continew.starter.extension.crud.annotation.TreeField; import top.continew.starter.extension.crud.annotation.TreeField;
import top.continew.starter.extension.crud.util.TreeUtils; import top.continew.starter.extension.crud.util.TreeUtils;
import java.time.Duration;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.util.*; import java.util.*;
@ -77,16 +78,41 @@ public class LoginServiceImpl implements LoginService {
private final UserSocialService userSocialService; private final UserSocialService userSocialService;
private final MessageService messageService; private final MessageService messageService;
private final PasswordEncoder passwordEncoder; private final PasswordEncoder passwordEncoder;
private final OptionService optionService;
@Override @Override
public String accountLogin(String username, String password) { public String accountLogin(String username, String password) {
UserDO user = userService.getByUsername(username); UserDO user = userService.getByUsername(username);
CheckUtils.throwIfNull(user, "用户名或密码不正确"); boolean isError = ObjectUtil.isNull(user) || !passwordEncoder.matches(password, user.getPassword());
CheckUtils.throwIf(!passwordEncoder.matches(password, user.getPassword()), "用户名或密码不正确"); isPasswordLocked(username, isError);
CheckUtils.throwIf(isError, "用户名或密码错误");
this.checkUserStatus(user); this.checkUserStatus(user);
return this.login(user); return this.login(user);
} }
/**
* 检测用户是否被密码锁定
*
* @param username 用户名
*/
private void isPasswordLocked(String username, boolean isError) {
// 不锁定账户
int maxErrorCount = optionService.getValueByCode2Int(OptionCodeEnum.PASSWORD_ERROR_COUNT);
if (maxErrorCount <= 0) {
return;
}
String key = CacheConstants.USER_KEY_PREFIX + "PASSWORD-ERROR:" + username;
Long currentErrorCount = RedisUtils.get(key);
currentErrorCount = currentErrorCount == null ? 0 : currentErrorCount;
int lockMinutes = optionService.getValueByCode2Int(OptionCodeEnum.PASSWORD_LOCK_MINUTES);
if (isError) {
// 密码错误自增次数并重置时间
currentErrorCount = currentErrorCount + 1;
RedisUtils.set(key, currentErrorCount, Duration.ofMinutes(lockMinutes));
}
CheckUtils.throwIf(currentErrorCount >= maxErrorCount, "密码错误已达 {} 次,账户锁定 {} 分钟", maxErrorCount, lockMinutes);
}
@Override @Override
public String phoneLogin(String phone) { public String phoneLogin(String phone) {
UserDO user = userService.getByPhone(phone); UserDO user = userService.getByPhone(phone);

View File

@ -0,0 +1,77 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
*
* 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.
*/
package com.zayac.admin.system.enums;/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
*
* 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.
*/
import lombok.Getter;
import lombok.RequiredArgsConstructor;
/**
* 参数枚举
*
* @author Kils
* @since 2024/05/09 11:25
*/
@Getter
@RequiredArgsConstructor
public enum OptionCodeEnum {
/**
* 密码是否允许包含正反序帐户名
*/
PASSWORD_CONTAIN_NAME("password_contain_name", "密码不允许包含正反序帐户名"),
/**
* 密码错误锁定帐户次数
*/
PASSWORD_ERROR_COUNT("password_error_count", "密码错误锁定帐户次数"),
/**
* 密码有效期
*/
PASSWORD_EXPIRATION_DAYS("password_expiration_days", "密码有效期"),
/**
* 密码是否允许包含正反序帐户名
*/
PASSWORD_LOCK_MINUTES("password_lock_minutes", "密码错误锁定帐户的时间"),
/**
* 密码最小长度
*/
PASSWORD_MIN_LENGTH("password_min_length", "密码最小长度"),
/**
* 密码是否必须包含特殊字符
*/
PASSWORD_SPECIAL_CHAR("password_special_char", "密码是否必须包含特殊字符"),
/**
* 修改密码最短间隔
*/
PASSWORD_UPDATE_INTERVAL("password_update_interval", "修改密码最短间隔");
private final String value;
private final String description;
}

View File

@ -17,12 +17,17 @@
package com.zayac.admin.system.mapper; package com.zayac.admin.system.mapper;
import com.zayac.admin.system.model.entity.AccountDO; import com.zayac.admin.system.model.entity.AccountDO;
import com.zayac.admin.system.model.resp.AccountResp;
import top.continew.starter.data.mybatis.plus.base.BaseMapper; import top.continew.starter.data.mybatis.plus.base.BaseMapper;
import java.util.List;
/** /**
* 账号 Mapper * 账号 Mapper
* *
* @author zayac * @author zayac
* @since 2024/05/10 20:44 * @since 2024/05/10 20:44
*/ */
public interface AccountMapper extends BaseMapper<AccountDO> {} public interface AccountMapper extends BaseMapper<AccountDO> {
List<AccountResp> selectAccountsByUserIds(List<Long> userIds);
}

View File

@ -17,8 +17,12 @@
package com.zayac.admin.system.mapper; package com.zayac.admin.system.mapper;
import com.zayac.admin.system.model.entity.DeptDO; import com.zayac.admin.system.model.entity.DeptDO;
import com.zayac.admin.system.model.resp.DeptUsersResp;
import org.apache.ibatis.annotations.Param;
import top.continew.starter.data.mybatis.plus.base.BaseMapper; import top.continew.starter.data.mybatis.plus.base.BaseMapper;
import java.util.List;
/** /**
* 部门 Mapper * 部门 Mapper
* *
@ -26,4 +30,11 @@ import top.continew.starter.data.mybatis.plus.base.BaseMapper;
* @since 2023/1/22 17:56 * @since 2023/1/22 17:56
*/ */
public interface DeptMapper extends BaseMapper<DeptDO> { public interface DeptMapper extends BaseMapper<DeptDO> {
/**
* 查询指定指定角色下的所有所属部门用户
*
* @param roleCode 角色列表
* @return 部门用户列表
*/
List<DeptUsersResp> selectDeptUsersByRoleCode(@Param("roleCode") String roleCode);
} }

View File

@ -16,6 +16,7 @@
package com.zayac.admin.system.mapper; package com.zayac.admin.system.mapper;
import com.zayac.admin.system.model.entity.RoleDO;
import com.zayac.admin.system.model.entity.RoleDeptDO; import com.zayac.admin.system.model.entity.RoleDeptDO;
import org.apache.ibatis.annotations.Param; import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select; import org.apache.ibatis.annotations.Select;
@ -39,4 +40,6 @@ public interface RoleDeptMapper extends BaseMapper<RoleDeptDO> {
*/ */
@Select("SELECT dept_id FROM sys_role_dept WHERE role_id = #{roleId}") @Select("SELECT dept_id FROM sys_role_dept WHERE role_id = #{roleId}")
List<Long> selectDeptIdByRoleId(@Param("roleId") Long roleId); List<Long> selectDeptIdByRoleId(@Param("roleId") Long roleId);
List<RoleDO> selectRolesByDeptIds(List<Long> deptIds);
} }

View File

@ -26,6 +26,8 @@ import com.zayac.admin.system.model.entity.UserDO;
import top.continew.starter.data.mybatis.plus.datapermission.DataPermission; import top.continew.starter.data.mybatis.plus.datapermission.DataPermission;
import top.continew.starter.security.crypto.annotation.FieldEncrypt; import top.continew.starter.security.crypto.annotation.FieldEncrypt;
import java.util.List;
/** /**
* 用户 Mapper * 用户 Mapper
* *
@ -96,4 +98,13 @@ public interface UserMapper extends DataPermissionMapper<UserDO> {
* @return 用户数量 * @return 用户数量
*/ */
Long selectCountByPhone(@FieldEncrypt @Param("phone") String phone, @Param("id") Long id); Long selectCountByPhone(@FieldEncrypt @Param("phone") String phone, @Param("id") Long id);
/**
* 根据部门id查询直系用户
*
* @param deptIds 部门id
* @return 直系用户列表
*/
List<UserDO> selectUsersByDeptIds(@Param("deptIds") List<Long> deptIds);
} }

View File

@ -16,6 +16,7 @@
package com.zayac.admin.system.mapper; package com.zayac.admin.system.mapper;
import com.zayac.admin.system.model.entity.RoleDO;
import com.zayac.admin.system.model.entity.UserRoleDO; import com.zayac.admin.system.model.entity.UserRoleDO;
import org.apache.ibatis.annotations.Param; import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select; import org.apache.ibatis.annotations.Select;
@ -48,4 +49,6 @@ public interface UserRoleMapper extends BaseMapper<UserRoleDO> {
*/ */
@Select("SELECT user_id FROM sys_user_role WHERE role_id = #{roleId}") @Select("SELECT user_id FROM sys_user_role WHERE role_id = #{roleId}")
List<Long> selectUserIdByRoleId(@Param("roleId") Long roleId); List<Long> selectUserIdByRoleId(@Param("roleId") Long roleId);
List<RoleDO> selectRolesByUserIds(List<Long> userIds);
} }

View File

@ -78,9 +78,4 @@ public class AccountDO extends BaseDO {
* 是否为团队账号 * 是否为团队账号
*/ */
private Boolean isTeam; private Boolean isTeam;
// /**
// * 消息通知对象IDs
// */
// private String receiverIds;
} }

View File

@ -26,7 +26,6 @@ import top.continew.starter.security.crypto.annotation.FieldEncrypt;
import java.io.Serial; import java.io.Serial;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.util.List;
/** /**
* 用户实体 * 用户实体

View File

@ -45,7 +45,7 @@ public class UserEmailUpdateRequest implements Serializable {
@Schema(description = "新邮箱", example = "123456789@qq.com") @Schema(description = "新邮箱", example = "123456789@qq.com")
@NotBlank(message = "新邮箱不能为空") @NotBlank(message = "新邮箱不能为空")
@Pattern(regexp = RegexPool.EMAIL, message = "邮箱格式错误") @Pattern(regexp = RegexPool.EMAIL, message = "邮箱格式错误")
private String newEmail; private String email;
/** /**
* 验证码 * 验证码
@ -60,5 +60,5 @@ public class UserEmailUpdateRequest implements Serializable {
*/ */
@Schema(description = "当前密码(加密)", example = "SYRLSszQGcMv4kP2Yolou9zf28B9GDakR9u91khxmR7V++i5A384kwnNZxqgvT6bjT4zqpIDuMFLWSt92hQJJA==") @Schema(description = "当前密码(加密)", example = "SYRLSszQGcMv4kP2Yolou9zf28B9GDakR9u91khxmR7V++i5A384kwnNZxqgvT6bjT4zqpIDuMFLWSt92hQJJA==")
@NotBlank(message = "当前密码不能为空") @NotBlank(message = "当前密码不能为空")
private String currentPassword; private String oldPassword;
} }

View File

@ -45,7 +45,7 @@ public class UserPhoneUpdateReq implements Serializable {
@Schema(description = "新手机号", example = "13811111111") @Schema(description = "新手机号", example = "13811111111")
@NotBlank(message = "新手机号不能为空") @NotBlank(message = "新手机号不能为空")
@Pattern(regexp = RegexPool.MOBILE, message = "手机号格式错误") @Pattern(regexp = RegexPool.MOBILE, message = "手机号格式错误")
private String newPhone; private String phone;
/** /**
* 验证码 * 验证码
@ -60,5 +60,5 @@ public class UserPhoneUpdateReq implements Serializable {
*/ */
@Schema(description = "当前密码(加密)", example = "SYRLSszQGcMv4kP2Yolou9zf28B9GDakR9u91khxmR7V++i5A384kwnNZxqgvT6bjT4zqpIDuMFLWSt92hQJJA==") @Schema(description = "当前密码(加密)", example = "SYRLSszQGcMv4kP2Yolou9zf28B9GDakR9u91khxmR7V++i5A384kwnNZxqgvT6bjT4zqpIDuMFLWSt92hQJJA==")
@NotBlank(message = "当前密码不能为空") @NotBlank(message = "当前密码不能为空")
private String currentPassword; private String oldPassword;
} }

View File

@ -0,0 +1,34 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
*
* 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.
*/
package com.zayac.admin.system.model.resp;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
import java.util.List;
@Data
public class DeptUsersResp implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
private Long deptId;
private String deptName;
private List<UserWithRolesAndAccountsResp> users;
}

View File

@ -0,0 +1,39 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
*
* 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.
*/
package com.zayac.admin.system.model.resp;
import com.zayac.admin.common.enums.DisEnableStatusEnum;
import com.zayac.admin.system.model.entity.RoleDO;
import lombok.Data;
import java.util.List;
@Data
public class UserWithRolesAndAccountsResp {
private Long userId;
private String username;
private String nickname;
private DisEnableStatusEnum needNotify;
private String botToken;
private String telegramIds;
private String regAndDepIds;
private String reportIds;
private Long deptId;
private String deptName;
private List<RoleDO> roles;
private List<AccountResp> accounts;
}

View File

@ -16,8 +16,6 @@
package com.zayac.admin.system.service; package com.zayac.admin.system.service;
import com.zayac.admin.common.enums.DisEnableStatusEnum;
import com.zayac.admin.system.model.entity.UserDO;
import com.zayac.admin.system.model.query.AccountQuery; import com.zayac.admin.system.model.query.AccountQuery;
import com.zayac.admin.system.model.req.AccountReq; import com.zayac.admin.system.model.req.AccountReq;
import com.zayac.admin.system.model.resp.AccountDetailResp; import com.zayac.admin.system.model.resp.AccountDetailResp;
@ -33,9 +31,8 @@ import java.util.List;
* @since 2024/05/10 20:44 * @since 2024/05/10 20:44
*/ */
public interface AccountService extends BaseService<AccountResp, AccountDetailResp, AccountQuery, AccountReq> { public interface AccountService extends BaseService<AccountResp, AccountDetailResp, AccountQuery, AccountReq> {
List<AccountResp> getAccountsByUserId(Long id, DisEnableStatusEnum status);
UserDO getUserByAccountUsername(String username);
void updateHeaders(String headers, Long id); void updateHeaders(String headers, Long id);
List<AccountResp> getAccountsByUserIds(List<Long> userIds);
} }

View File

@ -20,6 +20,7 @@ import com.zayac.admin.system.model.entity.DeptDO;
import com.zayac.admin.system.model.query.DeptQuery; import com.zayac.admin.system.model.query.DeptQuery;
import com.zayac.admin.system.model.req.DeptReq; import com.zayac.admin.system.model.req.DeptReq;
import com.zayac.admin.system.model.resp.DeptResp; import com.zayac.admin.system.model.resp.DeptResp;
import com.zayac.admin.system.model.resp.DeptUsersResp;
import top.continew.starter.data.mybatis.plus.service.IService; import top.continew.starter.data.mybatis.plus.service.IService;
import top.continew.starter.extension.crud.service.BaseService; import top.continew.starter.extension.crud.service.BaseService;
@ -41,5 +42,5 @@ public interface DeptService extends BaseService<DeptResp, DeptResp, DeptQuery,
*/ */
List<DeptDO> listChildren(Long id); List<DeptDO> listChildren(Long id);
List<DeptDO> getByName(String name); List<DeptUsersResp> getDeptWithUsersAndAccounts(String roleCode);
} }

View File

@ -17,7 +17,9 @@
package com.zayac.admin.system.service; package com.zayac.admin.system.service;
import java.util.List; import java.util.List;
import java.util.function.Function;
import com.zayac.admin.system.enums.OptionCodeEnum;
import com.zayac.admin.system.model.query.OptionQuery; import com.zayac.admin.system.model.query.OptionQuery;
import com.zayac.admin.system.model.req.OptionReq; import com.zayac.admin.system.model.req.OptionReq;
import com.zayac.admin.system.model.req.OptionResetValueReq; import com.zayac.admin.system.model.req.OptionResetValueReq;
@ -52,4 +54,21 @@ public interface OptionService {
* @param req 重置信息 * @param req 重置信息
*/ */
void resetValue(OptionResetValueReq req); void resetValue(OptionResetValueReq req);
/**
* 根据code获取int参数值
*
* @param code code
* @return 参数值
*/
int getValueByCode2Int(OptionCodeEnum code);
/**
* 根据code获取参数值
*
* @param code code
* @param mapper 类型转换 ex:value -> Integer.parseInt(value)
* @return 参数值
*/
<T> T getValueByCode(OptionCodeEnum code, Function<String, T> mapper);
} }

View File

@ -16,6 +16,8 @@
package com.zayac.admin.system.service; package com.zayac.admin.system.service;
import com.zayac.admin.system.model.entity.RoleDO;
import java.util.List; import java.util.List;
/** /**
@ -56,4 +58,6 @@ public interface RoleDeptService {
* @return 部门 ID 列表 * @return 部门 ID 列表
*/ */
List<Long> listDeptIdByRoleId(Long roleId); List<Long> listDeptIdByRoleId(Long roleId);
List<RoleDO> getRolesByDeptIds(List<Long> deptIds);
} }

View File

@ -16,6 +16,9 @@
package com.zayac.admin.system.service; package com.zayac.admin.system.service;
import com.zayac.admin.system.model.entity.RoleDO;
import com.zayac.admin.system.model.entity.UserDO;
import java.util.List; import java.util.List;
/** /**
@ -58,5 +61,7 @@ public interface UserRoleService {
*/ */
boolean isRoleIdExists(List<Long> roleIds); boolean isRoleIdExists(List<Long> roleIds);
List<Long> listUserIdByRoleId(Long roleId); List<UserDO> getsUserByRoleCode(String roleCode);
List<RoleDO> getRolesByUserIds(List<Long> collect);
} }

View File

@ -16,7 +16,6 @@
package com.zayac.admin.system.service; package com.zayac.admin.system.service;
import com.zayac.admin.common.enums.DisEnableStatusEnum;
import com.zayac.admin.common.model.resp.LabelValueResp; import com.zayac.admin.common.model.resp.LabelValueResp;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import com.zayac.admin.system.model.entity.UserDO; import com.zayac.admin.system.model.entity.UserDO;
@ -30,6 +29,7 @@ import com.zayac.admin.system.model.resp.UserResp;
import top.continew.starter.extension.crud.service.BaseService; import top.continew.starter.extension.crud.service.BaseService;
import top.continew.starter.data.mybatis.plus.service.IService; import top.continew.starter.data.mybatis.plus.service.IService;
import java.time.LocalDateTime;
import java.util.List; import java.util.List;
/** /**
@ -77,20 +77,20 @@ public interface UserService extends BaseService<UserResp, UserDetailResp, UserQ
/** /**
* 修改手机号 * 修改手机号
* *
* @param newPhone 新手机号 * @param newPhone 新手机号
* @param currentPassword 当前密码 * @param oldPassword 当前密码
* @param id ID * @param id ID
*/ */
void updatePhone(String newPhone, String currentPassword, Long id); void updatePhone(String newPhone, String oldPassword, Long id);
/** /**
* 修改邮箱 * 修改邮箱
* *
* @param newEmail 新邮箱 * @param newEmail 新邮箱
* @param currentPassword 当前密码 * @param oldPassword 当前密码
* @param id ID * @param id ID
*/ */
void updateEmail(String newEmail, String currentPassword, Long id); void updateEmail(String newEmail, String oldPassword, Long id);
/** /**
* 重置密码 * 重置密码
@ -140,6 +140,14 @@ public interface UserService extends BaseService<UserResp, UserDetailResp, UserQ
*/ */
Long countByDeptIds(List<Long> deptIds); Long countByDeptIds(List<Long> deptIds);
/**
* 密码是否已过期
*
* @param pwdResetTime 上次重置密码时间
* @return 是否过期
*/
Boolean isPasswordExpired(LocalDateTime pwdResetTime);
/** /**
* 构建字典 * 构建字典
* *
@ -148,16 +156,4 @@ public interface UserService extends BaseService<UserResp, UserDetailResp, UserQ
*/ */
List<LabelValueResp<Long>> buildDict(List<UserResp> list); List<LabelValueResp<Long>> buildDict(List<UserResp> list);
/**
* 查询所有的可用用户
*
* @return 用户列表
*/
List<UserDO> getAllEnabledUsers();
/**
* 查询所有部门用户
*/
List<UserDO> getByDeptId(DisEnableStatusEnum status, Long deptId);
} }

View File

@ -16,19 +16,13 @@
package com.zayac.admin.system.service.impl; package com.zayac.admin.system.service.impl;
import com.alicp.jetcache.anno.CacheType;
import com.alicp.jetcache.anno.Cached;
import com.zayac.admin.common.constant.CacheConstants;
import com.zayac.admin.common.enums.DisEnableStatusEnum;
import com.zayac.admin.system.mapper.AccountMapper; import com.zayac.admin.system.mapper.AccountMapper;
import com.zayac.admin.system.model.entity.AccountDO; import com.zayac.admin.system.model.entity.AccountDO;
import com.zayac.admin.system.model.entity.UserDO;
import com.zayac.admin.system.model.query.AccountQuery; import com.zayac.admin.system.model.query.AccountQuery;
import com.zayac.admin.system.model.req.AccountReq; import com.zayac.admin.system.model.req.AccountReq;
import com.zayac.admin.system.model.resp.AccountDetailResp; import com.zayac.admin.system.model.resp.AccountDetailResp;
import com.zayac.admin.system.model.resp.AccountResp; import com.zayac.admin.system.model.resp.AccountResp;
import com.zayac.admin.system.service.AccountService; import com.zayac.admin.system.service.AccountService;
import com.zayac.admin.system.service.UserService;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
@ -46,30 +40,14 @@ import java.util.List;
@Service @Service
@RequiredArgsConstructor @RequiredArgsConstructor
public class AccountServiceImpl extends BaseServiceImpl<AccountMapper, AccountDO, AccountResp, AccountDetailResp, AccountQuery, AccountReq> implements AccountService { public class AccountServiceImpl extends BaseServiceImpl<AccountMapper, AccountDO, AccountResp, AccountDetailResp, AccountQuery, AccountReq> implements AccountService {
private final UserService userService;
@Override
@Cached(cacheType = CacheType.LOCAL, key = "#userId", name = CacheConstants.ENABLED_ACCOUNTS_KEY_PREFIX, localExpire = 300, expire = 300)
public List<AccountResp> getAccountsByUserId(Long id, DisEnableStatusEnum status) {
AccountQuery accountQuery = new AccountQuery();
accountQuery.setStatus(status.getValue());
accountQuery.setUserId(id);
return list(accountQuery, null);
}
@Override
@Cached(cacheType = CacheType.LOCAL, key = "#username", name = CacheConstants.ENABLED_ACCOUNTS_KEY_PREFIX, localExpire = 300, expire = 300)
public UserDO getUserByAccountUsername(String username) {
return baseMapper.lambdaQuery()
.eq(AccountDO::getUsername, username)
.eq(AccountDO::getStatus, DisEnableStatusEnum.ENABLE)
.oneOpt()
.map(account -> userService.getById(account.getUserId()))
.orElse(null);
}
@Override @Override
public void updateHeaders(String headers, Long id) { public void updateHeaders(String headers, Long id) {
baseMapper.lambdaUpdate().set(AccountDO::getHeaders, headers).eq(AccountDO::getId, id).update(); baseMapper.lambdaUpdate().set(AccountDO::getHeaders, headers).eq(AccountDO::getId, id).update();
} }
@Override
public List<AccountResp> getAccountsByUserIds(List<Long> userIds) {
return baseMapper.selectAccountsByUserIds(userIds);
}
} }

View File

@ -19,10 +19,12 @@ package com.zayac.admin.system.service.impl;
import cn.hutool.core.collection.CollUtil; import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.ObjectUtil;
import cn.hutool.extra.spring.SpringUtil; import cn.hutool.extra.spring.SpringUtil;
import com.alicp.jetcache.anno.CacheType;
import com.alicp.jetcache.anno.Cached;
import com.baomidou.dynamic.datasource.DynamicRoutingDataSource; import com.baomidou.dynamic.datasource.DynamicRoutingDataSource;
import com.zayac.admin.system.service.DeptService; import com.zayac.admin.common.constant.CacheConstants;
import com.zayac.admin.system.service.RoleDeptService; import com.zayac.admin.system.model.resp.DeptUsersResp;
import com.zayac.admin.system.service.UserService; import com.zayac.admin.system.service.*;
import jakarta.annotation.Resource; import jakarta.annotation.Resource;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
@ -37,9 +39,7 @@ import top.continew.starter.data.core.enums.DatabaseType;
import top.continew.starter.data.core.util.MetaUtils; import top.continew.starter.data.core.util.MetaUtils;
import top.continew.starter.extension.crud.service.impl.BaseServiceImpl; import top.continew.starter.extension.crud.service.impl.BaseServiceImpl;
import java.util.ArrayList; import java.util.*;
import java.util.List;
import java.util.Optional;
/** /**
* 部门业务实现 * 部门业务实现
@ -54,6 +54,7 @@ public class DeptServiceImpl extends BaseServiceImpl<DeptMapper, DeptDO, DeptRes
@Resource @Resource
private UserService userService; private UserService userService;
private final RoleDeptService roleDeptService; private final RoleDeptService roleDeptService;
private final DeptMapper baseMapper;
@Override @Override
public List<DeptDO> listChildren(Long id) { public List<DeptDO> listChildren(Long id) {
@ -62,11 +63,6 @@ public class DeptServiceImpl extends BaseServiceImpl<DeptMapper, DeptDO, DeptRes
return baseMapper.lambdaQuery().apply(databaseType.findInSet(id, "ancestors")).list(); return baseMapper.lambdaQuery().apply(databaseType.findInSet(id, "ancestors")).list();
} }
@Override
public List<DeptDO> getByName(String name) {
return baseMapper.lambdaQuery().eq(DeptDO::getName, name).list();
}
@Override @Override
protected void beforeAdd(DeptReq req) { protected void beforeAdd(DeptReq req) {
String name = req.getName(); String name = req.getName();
@ -202,4 +198,10 @@ public class DeptServiceImpl extends BaseServiceImpl<DeptMapper, DeptDO, DeptRes
} }
baseMapper.updateBatchById(list); baseMapper.updateBatchById(list);
} }
@Cached(key = "#roleCode", cacheType = CacheType.LOCAL, name = CacheConstants.DEPT_USERS_ROLES_ACCOUNTS_KEY_PREFIX, localExpire = 360)
public List<DeptUsersResp> getDeptWithUsersAndAccounts(String roleCode) {
return baseMapper.selectDeptUsersByRoleCode(roleCode);
}
} }

View File

@ -17,6 +17,11 @@
package com.zayac.admin.system.service.impl; package com.zayac.admin.system.service.impl;
import cn.hutool.core.bean.BeanUtil; import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.ObjUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.zayac.admin.system.enums.OptionCodeEnum;
import com.zayac.admin.system.service.OptionService; import com.zayac.admin.system.service.OptionService;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
@ -29,9 +34,11 @@ import com.zayac.admin.system.model.req.OptionResetValueReq;
import com.zayac.admin.system.model.resp.OptionResp; import com.zayac.admin.system.model.resp.OptionResp;
import top.continew.starter.cache.redisson.util.RedisUtils; import top.continew.starter.cache.redisson.util.RedisUtils;
import top.continew.starter.core.constant.StringConstants; import top.continew.starter.core.constant.StringConstants;
import top.continew.starter.core.util.validate.CheckUtils;
import top.continew.starter.data.mybatis.plus.query.QueryWrapperHelper; import top.continew.starter.data.mybatis.plus.query.QueryWrapperHelper;
import java.util.List; import java.util.List;
import java.util.function.Function;
/** /**
* 参数业务实现 * 参数业务实现
@ -61,4 +68,27 @@ public class OptionServiceImpl implements OptionService {
RedisUtils.deleteByPattern(CacheConstants.OPTION_KEY_PREFIX + StringConstants.ASTERISK); RedisUtils.deleteByPattern(CacheConstants.OPTION_KEY_PREFIX + StringConstants.ASTERISK);
baseMapper.lambdaUpdate().set(OptionDO::getValue, null).in(OptionDO::getCode, req.getCode()).update(); baseMapper.lambdaUpdate().set(OptionDO::getValue, null).in(OptionDO::getCode, req.getCode()).update();
} }
@Override
public int getValueByCode2Int(OptionCodeEnum code) {
return this.getValueByCode(code, Integer::parseInt);
}
@Override
public <T> T getValueByCode(OptionCodeEnum code, Function<String, T> mapper) {
String value = RedisUtils.get(CacheConstants.OPTION_KEY_PREFIX + code.getValue());
if (StrUtil.isNotBlank(value)) {
return mapper.apply(value);
}
LambdaQueryWrapper<OptionDO> queryWrapper = Wrappers.<OptionDO>lambdaQuery()
.eq(OptionDO::getCode, code.getValue())
.select(OptionDO::getValue, OptionDO::getDefaultValue);
OptionDO optionDO = baseMapper.selectOne(queryWrapper);
CheckUtils.throwIf(ObjUtil.isEmpty(optionDO), "配置 [{}] 不存在", code);
value = StrUtil.nullToDefault(optionDO.getValue(), optionDO.getDefaultValue());
CheckUtils.throwIf(StrUtil.isBlank(value), "配置 [{}] 不存在", code);
RedisUtils.set(CacheConstants.OPTION_KEY_PREFIX + code.getValue(), value);
return mapper.apply(value);
}
} }

View File

@ -19,6 +19,7 @@ package com.zayac.admin.system.service.impl;
import cn.crane4j.annotation.ContainerMethod; import cn.crane4j.annotation.ContainerMethod;
import cn.crane4j.annotation.MappingType; import cn.crane4j.annotation.MappingType;
import cn.hutool.core.collection.CollUtil; import cn.hutool.core.collection.CollUtil;
import com.zayac.admin.system.model.entity.RoleDO;
import com.zayac.admin.system.service.RoleDeptService; import com.zayac.admin.system.service.RoleDeptService;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
@ -74,6 +75,11 @@ public class RoleDeptServiceImpl implements RoleDeptService {
baseMapper.lambdaUpdate().in(RoleDeptDO::getDeptId, deptIds).remove(); baseMapper.lambdaUpdate().in(RoleDeptDO::getDeptId, deptIds).remove();
} }
@Override
public List<RoleDO> getRolesByDeptIds(List<Long> deptIds) {
return baseMapper.selectRolesByDeptIds(deptIds);
}
@Override @Override
@ContainerMethod(namespace = ContainerConstants.ROLE_DEPT_ID_LIST, type = MappingType.ORDER_OF_KEYS) @ContainerMethod(namespace = ContainerConstants.ROLE_DEPT_ID_LIST, type = MappingType.ORDER_OF_KEYS)
public List<Long> listDeptIdByRoleId(Long roleId) { public List<Long> listDeptIdByRoleId(Long roleId) {

View File

@ -19,6 +19,8 @@ package com.zayac.admin.system.service.impl;
import cn.crane4j.annotation.ContainerMethod; import cn.crane4j.annotation.ContainerMethod;
import cn.crane4j.annotation.MappingType; import cn.crane4j.annotation.MappingType;
import cn.hutool.core.collection.CollUtil; import cn.hutool.core.collection.CollUtil;
import com.zayac.admin.system.model.entity.RoleDO;
import com.zayac.admin.system.model.entity.UserDO;
import com.zayac.admin.system.service.UserRoleService; import com.zayac.admin.system.service.UserRoleService;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
@ -74,14 +76,18 @@ public class UserRoleServiceImpl implements UserRoleService {
return baseMapper.selectRoleIdByUserId(userId); return baseMapper.selectRoleIdByUserId(userId);
} }
@Override
@ContainerMethod(namespace = ContainerConstants.ROLE_USER_ID_LIST, type = MappingType.ORDER_OF_KEYS)
public List<Long> listUserIdByRoleId(Long roleId) {
return baseMapper.selectUserIdByRoleId(roleId);
}
@Override @Override
public boolean isRoleIdExists(List<Long> roleIds) { public boolean isRoleIdExists(List<Long> roleIds) {
return baseMapper.lambdaQuery().in(UserRoleDO::getRoleId, roleIds).exists(); return baseMapper.lambdaQuery().in(UserRoleDO::getRoleId, roleIds).exists();
} }
@Override
public List<UserDO> getsUserByRoleCode(String roleCode) {
return null;
}
@Override
public List<RoleDO> getRolesByUserIds(List<Long> userIds) {
return baseMapper.selectRolesByUserIds(userIds);
}
} }

View File

@ -18,8 +18,10 @@ package com.zayac.admin.system.service.impl;
import cn.hutool.core.bean.BeanUtil; import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil; import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.core.io.file.FileNameUtil; import cn.hutool.core.io.file.FileNameUtil;
import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.ReUtil;
import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.StrUtil;
import com.alicp.jetcache.anno.CacheInvalidate; import com.alicp.jetcache.anno.CacheInvalidate;
import com.alicp.jetcache.anno.CacheType; import com.alicp.jetcache.anno.CacheType;
@ -27,6 +29,7 @@ import com.alicp.jetcache.anno.CacheUpdate;
import com.alicp.jetcache.anno.Cached; import com.alicp.jetcache.anno.Cached;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.core.metadata.IPage;
import com.zayac.admin.common.constant.RegexConstants;
import com.zayac.admin.common.model.resp.LabelValueResp; import com.zayac.admin.common.model.resp.LabelValueResp;
import com.zayac.admin.system.mapper.UserMapper; import com.zayac.admin.system.mapper.UserMapper;
import com.zayac.admin.system.service.*; import com.zayac.admin.system.service.*;
@ -52,9 +55,9 @@ import com.zayac.admin.system.model.req.UserReq;
import com.zayac.admin.system.model.req.UserRoleUpdateReq; import com.zayac.admin.system.model.req.UserRoleUpdateReq;
import com.zayac.admin.system.model.resp.UserDetailResp; import com.zayac.admin.system.model.resp.UserDetailResp;
import com.zayac.admin.system.model.resp.UserResp; import com.zayac.admin.system.model.resp.UserResp;
import com.zayac.admin.system.service.*;
import top.continew.starter.core.constant.StringConstants; import top.continew.starter.core.constant.StringConstants;
import top.continew.starter.core.util.validate.CheckUtils; import top.continew.starter.core.util.validate.CheckUtils;
import top.continew.starter.core.util.validate.ValidationUtils;
import top.continew.starter.extension.crud.model.query.PageQuery; import top.continew.starter.extension.crud.model.query.PageQuery;
import top.continew.starter.extension.crud.model.resp.PageResp; import top.continew.starter.extension.crud.model.resp.PageResp;
import top.continew.starter.extension.crud.service.CommonUserService; import top.continew.starter.extension.crud.service.CommonUserService;
@ -64,6 +67,8 @@ import java.time.LocalDateTime;
import java.util.*; import java.util.*;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import static com.zayac.admin.system.enums.OptionCodeEnum.*;
/** /**
* 用户业务实现 * 用户业务实现
* *
@ -80,6 +85,7 @@ public class UserServiceImpl extends BaseServiceImpl<UserMapper, UserDO, UserRes
private final FileService fileService; private final FileService fileService;
private final FileStorageService fileStorageService; private final FileStorageService fileStorageService;
private final PasswordEncoder passwordEncoder; private final PasswordEncoder passwordEncoder;
private final OptionService optionService;
@Resource @Resource
private DeptService deptService; private DeptService deptService;
@Value("${avatar.support-suffix}") @Value("${avatar.support-suffix}")
@ -192,16 +198,74 @@ public class UserServiceImpl extends BaseServiceImpl<UserMapper, UserDO, UserRes
if (StrUtil.isNotBlank(password)) { if (StrUtil.isNotBlank(password)) {
CheckUtils.throwIf(!passwordEncoder.matches(oldPassword, password), "当前密码错误"); CheckUtils.throwIf(!passwordEncoder.matches(oldPassword, password), "当前密码错误");
} }
// 校验密码合法性
checkPassword(newPassword, user);
// 更新密码和密码重置时间 // 更新密码和密码重置时间
user.setPassword(newPassword); user.setPassword(newPassword);
user.setPwdResetTime(LocalDateTime.now()); user.setPwdResetTime(LocalDateTime.now());
baseMapper.updateById(user); baseMapper.updateById(user);
onlineUserService.cleanByUserId(user.getId());
}
/**
* 检测修改密码合法性
*
* @param password 密码
* @param user 用户
*/
private void checkPassword(String password, UserDO user) {
// 密码最小长度
int passwordMinLength = optionService.getValueByCode2Int(PASSWORD_MIN_LENGTH);
ValidationUtils.throwIf(StrUtil.length(password) < passwordMinLength, PASSWORD_MIN_LENGTH
.getDescription() + "为 {}", passwordMinLength);
// 密码是否允许包含正反序用户名
int passwordContainName = optionService.getValueByCode2Int(PASSWORD_CONTAIN_NAME);
if (passwordContainName == 1) {
String username = user.getUsername();
ValidationUtils.throwIf(StrUtil.containsIgnoreCase(password, username) || StrUtil
.containsIgnoreCase(password, StrUtil.reverse(username)), PASSWORD_CONTAIN_NAME.getDescription());
}
// 密码是否必须包含特殊字符
int passwordSpecialChar = optionService.getValueByCode2Int(PASSWORD_SPECIAL_CHAR);
String match = RegexConstants.PASSWORD;
String desc = "密码长度为 6 到 32 位,可以包含字母、数字、下划线,特殊字符,同时包含字母和数字";
if (passwordSpecialChar == 1) {
match = RegexConstants.PASSWORD_STRICT;
desc = "密码长度为 8 到 32 位包含至少1个大写字母、1个小写字母、1个数字1个特殊字符";
}
ValidationUtils.throwIf(!ReUtil.isMatch(match, password), desc);
// 密码修改间隔
if (ObjectUtil.isNull(user.getPwdResetTime())) {
return;
}
int passwordUpdateInterval = optionService.getValueByCode2Int(PASSWORD_UPDATE_INTERVAL);
if (passwordUpdateInterval <= 0) {
return;
}
LocalDateTime lastResetTime = user.getPwdResetTime();
LocalDateTime limitUpdateTime = lastResetTime.plusMinutes(passwordUpdateInterval);
ValidationUtils.throwIf(LocalDateTime.now().isBefore(limitUpdateTime), "上次修改于:{},下次可修改时间:{}", LocalDateTimeUtil
.formatNormal(lastResetTime), LocalDateTimeUtil.formatNormal(limitUpdateTime));
} }
@Override @Override
public void updatePhone(String newPhone, String currentPassword, Long id) { public Boolean isPasswordExpired(LocalDateTime pwdResetTime) {
// 永久有效
int passwordExpirationDays = optionService.getValueByCode2Int(PASSWORD_EXPIRATION_DAYS);
if (passwordExpirationDays <= 0) {
return false;
}
// 初始密码也提示修改
if (pwdResetTime == null) {
return true;
}
return pwdResetTime.plusDays(passwordExpirationDays).isBefore(LocalDateTime.now());
}
@Override
public void updatePhone(String newPhone, String oldPassword, Long id) {
UserDO user = super.getById(id); UserDO user = super.getById(id);
CheckUtils.throwIf(!passwordEncoder.matches(currentPassword, user.getPassword()), "当前密码错误"); CheckUtils.throwIf(!passwordEncoder.matches(oldPassword, user.getPassword()), "当前密码错误");
CheckUtils.throwIf(this.isPhoneExists(newPhone, id), "手机号已绑定其他账号,请更换其他手机号"); CheckUtils.throwIf(this.isPhoneExists(newPhone, id), "手机号已绑定其他账号,请更换其他手机号");
CheckUtils.throwIfEqual(newPhone, user.getPhone(), "新手机号不能与当前手机号相同"); CheckUtils.throwIfEqual(newPhone, user.getPhone(), "新手机号不能与当前手机号相同");
// 更新手机号 // 更新手机号
@ -209,9 +273,9 @@ public class UserServiceImpl extends BaseServiceImpl<UserMapper, UserDO, UserRes
} }
@Override @Override
public void updateEmail(String newEmail, String currentPassword, Long id) { public void updateEmail(String newEmail, String oldPassword, Long id) {
UserDO user = super.getById(id); UserDO user = super.getById(id);
CheckUtils.throwIf(!passwordEncoder.matches(currentPassword, user.getPassword()), "当前密码错误"); CheckUtils.throwIf(!passwordEncoder.matches(oldPassword, user.getPassword()), "当前密码错误");
CheckUtils.throwIf(this.isEmailExists(newEmail, id), "邮箱已绑定其他账号,请更换其他邮箱"); CheckUtils.throwIf(this.isEmailExists(newEmail, id), "邮箱已绑定其他账号,请更换其他邮箱");
CheckUtils.throwIfEqual(newEmail, user.getEmail(), "新邮箱不能与当前邮箱相同"); CheckUtils.throwIfEqual(newEmail, user.getEmail(), "新邮箱不能与当前邮箱相同");
// 更新邮箱 // 更新邮箱
@ -254,19 +318,6 @@ public class UserServiceImpl extends BaseServiceImpl<UserMapper, UserDO, UserRes
return baseMapper.lambdaQuery().in(UserDO::getDeptId, deptIds).count(); return baseMapper.lambdaQuery().in(UserDO::getDeptId, deptIds).count();
} }
@Override
public List<LabelValueResp<Long>> buildDict(List<UserResp> list) {
if (CollUtil.isEmpty(list)) {
return new ArrayList<>(0);
}
return list.stream().map(r -> new LabelValueResp<>(r.getUsername(), r.getId())).toList();
}
@Override
public List<UserDO> getAllEnabledUsers() {
return baseMapper.lambdaQuery().eq(UserDO::getStatus, DisEnableStatusEnum.ENABLE.getValue()).list();
}
@Override @Override
@Cached(key = "#id", cacheType = CacheType.BOTH, name = CacheConstants.USER_KEY_PREFIX, syncLocal = true) @Cached(key = "#id", cacheType = CacheType.BOTH, name = CacheConstants.USER_KEY_PREFIX, syncLocal = true)
public String getNicknameById(Long id) { public String getNicknameById(Long id) {
@ -303,20 +354,6 @@ public class UserServiceImpl extends BaseServiceImpl<UserMapper, UserDO, UserRes
userRoleService.add(req.getRoleIds(), userId); userRoleService.add(req.getRoleIds(), userId);
} }
@Override
public List<UserDO> getByDeptId(DisEnableStatusEnum status, Long deptId) {
QueryWrapper<UserDO> wrapper = new QueryWrapper<UserDO>().eq(null != status, "status", status.getValue())
.and(q -> {
List<Long> deptIdList = deptService.listChildren(deptId)
.stream()
.map(DeptDO::getId)
.collect(Collectors.toList());
deptIdList.add(deptId);
q.in("dept_id", deptIdList);
});
return baseMapper.selectList(wrapper);
}
/** /**
* 构建 QueryWrapper * 构建 QueryWrapper
* *
@ -380,4 +417,12 @@ public class UserServiceImpl extends BaseServiceImpl<UserMapper, UserDO, UserRes
Long count = baseMapper.selectCountByPhone(phone, id); Long count = baseMapper.selectCountByPhone(phone, id);
return null != count && count > 0; return null != count && count > 0;
} }
}
@Override
public List<LabelValueResp<Long>> buildDict(List<UserResp> list) {
if (CollUtil.isEmpty(list)) {
return new ArrayList<>(0);
}
return list.stream().map(r -> new LabelValueResp<>(r.getUsername(), r.getId())).toList();
}
}

View File

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.zayac.admin.system.mapper.AccountMapper">
<select id="selectAccountsByUserIds" resultType="com.zayac.admin.system.model.resp.AccountResp">
SELECT
id, username, nickname, status, platform_id, is_team, user_id
FROM sys_account
WHERE user_id IN
<foreach item="userIds" collection="list" open="(" separator="," close=")">
#{userIds}
</foreach>;
</select>
</mapper>

View File

@ -1,4 +1,88 @@
<?xml version="1.0" encoding="UTF-8" ?> <?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" > <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.zayac.admin.system.mapper.DeptMapper"> <mapper namespace="com.zayac.admin.system.mapper.DeptMapper">
</mapper> <select id="selectDeptUsersByRoleCode" resultMap="DeptUserResultMap">
WITH RECURSIVE dept_hierarchy AS (SELECT d.id,
d.parent_id,
d.name,
d.id AS top_level_dept_id,
d.name AS top_level_dept_name
FROM sys_dept d
JOIN sys_user u ON u.dept_id = d.id
JOIN sys_user_role ur ON ur.user_id = u.id
JOIN sys_role r ON ur.role_id = r.id
WHERE d.status = 1
AND r.code = #{roleCode}
UNION ALL
SELECT d.id, d.parent_id, d.name, dh.top_level_dept_id, dh.top_level_dept_name
FROM sys_dept d
INNER JOIN dept_hierarchy dh ON d.parent_id = dh.id)
SELECT u.id AS user_id,
u.username,
u.nickname,
u.need_notify,
u.bot_token,
u.telegram_ids,
u.reg_and_dep_ids,
u.report_ids,
r.id AS role_id,
r.code AS role_code,
r.name AS role_name,
a.id AS account_id,
a.nickname AS account_nickname,
a.username AS account_username,
a.status AS account_status,
a.headers AS account_headers,
a.platform_id AS account_platform_id,
p.name AS platform_name,
p.url AS platform_url,
d.id AS dept_id,
d.name AS dept_name,
dh.top_level_dept_id,
dh.top_level_dept_name
FROM sys_user u
LEFT JOIN sys_user_role ur ON u.id = ur.user_id
LEFT JOIN sys_role r ON ur.role_id = r.id
LEFT JOIN sys_account a ON u.id = a.user_id AND a.status = 1
LEFT JOIN sys_platform p ON a.platform_id = p.id
LEFT JOIN sys_dept d ON u.dept_id = d.id
LEFT JOIN dept_hierarchy dh ON d.id = dh.id
WHERE u.status = 1
AND d.id IN (SELECT id FROM dept_hierarchy)
ORDER BY dh.top_level_dept_id, u.id, r.id, a.id;
</select>
<resultMap id="DeptUserResultMap" type="com.zayac.admin.system.model.resp.DeptUsersResp">
<id column="top_level_dept_id" property="deptId"/>
<result column="top_level_dept_name" property="deptName"/>
<collection property="users" ofType="com.zayac.admin.system.model.resp.UserWithRolesAndAccountsResp"
javaType="java.util.List">
<id column="user_id" property="userId"/>
<result column="username" property="username"/>
<result column="nickname" property="nickname"/>
<result column="need_notify" property="needNotify"/>
<result column="bot_token" property="botToken"/>
<result column="telegram_ids" property="telegramIds"/>
<result column="reg_and_dep_ids" property="regAndDepIds"/>
<result column="report_ids" property="reportIds"/>
<result column="dept_id" property="deptId"/>
<result column="dept_name" property="deptName"/>
<collection property="roles" ofType="com.zayac.admin.system.model.entity.RoleDO">
<id column="role_id" property="id"/>
<id column="role_code" property="code"/>
<result column="role_name" property="name"/>
</collection>
<collection property="accounts" ofType="com.zayac.admin.system.model.resp.AccountResp">
<id column="account_id" property="id"/>
<result column="account_nickname" property="nickname"/>
<result column="account_username" property="username"/>
<result column="account_status" property="status"/>
<result column="account_headers" property="headers"/>
<result column="account_platform_id" property="platformId"/>
<result column="platform_name" property="platformName"/>
<result column="platform_url" property="platformUrl"/>
</collection>
</collection>
</resultMap>
</mapper>

View File

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.zayac.admin.system.mapper.RoleDeptMapper">
<select id="selectRolesByDeptIds" resultType="com.zayac.admin.system.model.entity.RoleDO">
SELECT
rd.dept_id AS deptId,
r.id,
r.name,
r.code
FROM sys_role_dept rd
JOIN sys_role r ON rd.role_id = r.id
WHERE rd.dept_id IN
<foreach item="deptId" collection="deptIds" open="(" separator="," close=")">
#{deptId}
</foreach>;
</select>
</mapper>

View File

@ -1,4 +1,14 @@
<?xml version="1.0" encoding="UTF-8" ?> <?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" > <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.zayac.admin.system.mapper.RoleMapper"> <mapper namespace="com.zayac.admin.system.mapper.RoleMapper">
<select id="selectRolesByUserIds" resultType="com.zayac.admin.system.model.entity.RoleDO">
SELECT
r.id, r.name, r.code, ur.user_id AS userId
FROM sys_role r
JOIN sys_user_role ur ON r.id = ur.role_id
WHERE ur.user_id IN
<foreach item="userId" collection="userIds" open="(" separator="," close=")">
#{userId}
</foreach>;
</select>
</mapper> </mapper>

View File

@ -5,17 +5,17 @@
<select id="selectUserPage" resultType="com.zayac.admin.system.model.entity.UserDO"> <select id="selectUserPage" resultType="com.zayac.admin.system.model.entity.UserDO">
SELECT t1.* SELECT t1.*
FROM sys_user AS t1 FROM sys_user AS t1
LEFT JOIN sys_dept AS t2 ON t2.id = t1.dept_id LEFT JOIN sys_dept AS t2 ON t2.id = t1.dept_id
${ew.customSqlSegment} ${ew.customSqlSegment}
</select> </select>
<select id="selectCountByEmail" resultType="java.lang.Long"> <select id="selectCountByEmail" resultType="java.lang.Long">
SELECT count(*) SELECT count(*)
FROM sys_user FROM sys_user
WHERE email = #{email} WHERE email = #{email}
<if test="id != null"> <if test="id != null">
AND id != #{id} AND id != #{id}
</if> </if>
</select> </select>
<select id="selectCountByPhone" resultType="java.lang.Long"> <select id="selectCountByPhone" resultType="java.lang.Long">

View File

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.zayac.admin.system.mapper.UserRoleMapper">
<select id="selectRolesByUserIds" resultType="com.zayac.admin.system.model.entity.RoleDO">
SELECT
r.id, r.name, r.code, ur.user_id AS userId
FROM sys_role r
JOIN sys_user_role ur ON r.id = ur.role_id
WHERE ur.user_id IN
<foreach item="userId" collection="userIds" open="(" separator="," close=")">
#{userId}
</foreach>;
</select>
</mapper>

View File

@ -15,7 +15,7 @@
<properties> <properties>
<!-- ### 打包配置相关 ### --> <!-- ### 打包配置相关 ### -->
<!-- 启动类 --> <!-- 启动类 -->
<main-class>zayac.admin.ContiNewAdminApplication</main-class> <main-class>com.zayac.admin.ZayacAdminApplication</main-class>
<!-- 程序 jar 输出目录 --> <!-- 程序 jar 输出目录 -->
<bin-path>bin</bin-path> <bin-path>bin</bin-path>
<!-- 配置文件输出目录 --> <!-- 配置文件输出目录 -->

View File

@ -127,6 +127,7 @@ public class AuthController {
UserInfoResp userInfoResp = BeanUtil.copyProperties(userDetailResp, UserInfoResp.class); UserInfoResp userInfoResp = BeanUtil.copyProperties(userDetailResp, UserInfoResp.class);
userInfoResp.setPermissions(loginUser.getPermissions()); userInfoResp.setPermissions(loginUser.getPermissions());
userInfoResp.setRoles(loginUser.getRoleCodes()); userInfoResp.setRoles(loginUser.getRoleCodes());
userInfoResp.setPwdExpired(userService.isPasswordExpired(userDetailResp.getPwdResetTime()));
return R.ok(userInfoResp); return R.ok(userInfoResp);
} }

View File

@ -104,30 +104,30 @@ public class UserCenterController {
@Operation(summary = "修改手机号", description = "修改手机号") @Operation(summary = "修改手机号", description = "修改手机号")
@PatchMapping("/phone") @PatchMapping("/phone")
public R<Void> updatePhone(@Validated @RequestBody UserPhoneUpdateReq updateReq) { public R<Void> updatePhone(@Validated @RequestBody UserPhoneUpdateReq updateReq) {
String rawCurrentPassword = ExceptionUtils.exToNull(() -> SecureUtils.decryptByRsaPrivateKey(updateReq String rawOldPassword = ExceptionUtils.exToNull(() -> SecureUtils.decryptByRsaPrivateKey(updateReq
.getCurrentPassword())); .getOldPassword()));
ValidationUtils.throwIfBlank(rawCurrentPassword, DECRYPT_FAILED); ValidationUtils.throwIfBlank(rawOldPassword, DECRYPT_FAILED);
String captchaKey = CacheConstants.CAPTCHA_KEY_PREFIX + updateReq.getNewPhone(); String captchaKey = CacheConstants.CAPTCHA_KEY_PREFIX + updateReq.getPhone();
String captcha = RedisUtils.get(captchaKey); String captcha = RedisUtils.get(captchaKey);
ValidationUtils.throwIfBlank(captcha, CAPTCHA_EXPIRED); ValidationUtils.throwIfBlank(captcha, CAPTCHA_EXPIRED);
ValidationUtils.throwIfNotEqualIgnoreCase(updateReq.getCaptcha(), captcha, "验证码错误"); ValidationUtils.throwIfNotEqualIgnoreCase(updateReq.getCaptcha(), captcha, "验证码错误");
RedisUtils.delete(captchaKey); RedisUtils.delete(captchaKey);
userService.updatePhone(updateReq.getNewPhone(), rawCurrentPassword, LoginHelper.getUserId()); userService.updatePhone(updateReq.getPhone(), rawOldPassword, LoginHelper.getUserId());
return R.ok("修改成功"); return R.ok("修改成功");
} }
@Operation(summary = "修改邮箱", description = "修改用户邮箱") @Operation(summary = "修改邮箱", description = "修改用户邮箱")
@PatchMapping("/email") @PatchMapping("/email")
public R<Void> updateEmail(@Validated @RequestBody UserEmailUpdateRequest updateReq) { public R<Void> updateEmail(@Validated @RequestBody UserEmailUpdateRequest updateReq) {
String rawCurrentPassword = ExceptionUtils.exToNull(() -> SecureUtils.decryptByRsaPrivateKey(updateReq String rawOldPassword = ExceptionUtils.exToNull(() -> SecureUtils.decryptByRsaPrivateKey(updateReq
.getCurrentPassword())); .getOldPassword()));
ValidationUtils.throwIfBlank(rawCurrentPassword, DECRYPT_FAILED); ValidationUtils.throwIfBlank(rawOldPassword, DECRYPT_FAILED);
String captchaKey = CacheConstants.CAPTCHA_KEY_PREFIX + updateReq.getNewEmail(); String captchaKey = CacheConstants.CAPTCHA_KEY_PREFIX + updateReq.getEmail();
String captcha = RedisUtils.get(captchaKey); String captcha = RedisUtils.get(captchaKey);
ValidationUtils.throwIfBlank(captcha, CAPTCHA_EXPIRED); ValidationUtils.throwIfBlank(captcha, CAPTCHA_EXPIRED);
ValidationUtils.throwIfNotEqualIgnoreCase(updateReq.getCaptcha(), captcha, "验证码错误"); ValidationUtils.throwIfNotEqualIgnoreCase(updateReq.getCaptcha(), captcha, "验证码错误");
RedisUtils.delete(captchaKey); RedisUtils.delete(captchaKey);
userService.updateEmail(updateReq.getNewEmail(), rawCurrentPassword, LoginHelper.getUserId()); userService.updateEmail(updateReq.getEmail(), rawOldPassword, LoginHelper.getUserId());
return R.ok("修改成功"); return R.ok("修改成功");
} }

View File

@ -1,6 +1,6 @@
--- ### 项目配置 --- ### 项目配置
project: project:
# URL # URL(跨域配置默认放行此 URL第三方登录回调默认使用此 URL 为前缀,请注意更改为你实际的前端 URL
url: http://localhost:5173 url: http://localhost:5173
--- ### 服务器配置 --- ### 服务器配置
@ -58,7 +58,7 @@ spring.datasource:
## Liquibase 配置 ## Liquibase 配置
spring.liquibase: spring.liquibase:
# 是否启用 # 是否启用
enabled: true enabled: false
# 配置文件路径 # 配置文件路径
change-log: classpath:/db/changelog/db.changelog-master.yaml change-log: classpath:/db/changelog/db.changelog-master.yaml

View File

@ -1,6 +1,6 @@
--- ### 项目配置 --- ### 项目配置
project: project:
# URL跨域配置默认放行此 URL请注意更改为你实际的前端 URL # URL跨域配置默认放行此 URL第三方登录回调默认使用此 URL 为前缀,请注意更改为你实际的前端 URL
url: http://localhost:5173 url: http://localhost:5173
# 是否为生产环境 # 是否为生产环境
production: true production: true

View File

@ -114,7 +114,14 @@ VALUES
'Copyright © 2022-present&nbsp;<a href="https://blog.charles7c.top/about/me" target="_blank" rel="noopener">Charles7c</a>&nbsp;<span>⋅</span>&nbsp;<a href="https://github.com/Charles7c/continew-admin" target="_blank" rel="noopener">ContiNew Admin</a>&nbsp;<span>⋅</span>&nbsp;<a href="https://beian.miit.gov.cn" target="_blank" rel="noopener">津ICP备2022005864号-2</a>', 'Copyright © 2022-present&nbsp;<a href="https://blog.charles7c.top/about/me" target="_blank" rel="noopener">Charles7c</a>&nbsp;<span>⋅</span>&nbsp;<a href="https://github.com/Charles7c/continew-admin" target="_blank" rel="noopener">ContiNew Admin</a>&nbsp;<span>⋅</span>&nbsp;<a href="https://beian.miit.gov.cn" target="_blank" rel="noopener">津ICP备2022005864号-2</a>',
'用于显示登录页面的底部版权信息。', NULL, NULL), '用于显示登录页面的底部版权信息。', NULL, NULL),
('系统LOGO16*16', 'site_favicon', NULL, '/favicon.ico', '用于显示浏览器地址栏的系统LOGO。', NULL, NULL), ('系统LOGO16*16', 'site_favicon', NULL, '/favicon.ico', '用于显示浏览器地址栏的系统LOGO。', NULL, NULL),
('系统LOGO33*33', 'site_logo', NULL, '/logo.svg', '用于显示登录页面的系统LOGO。', NULL, NULL); ('系统LOGO33*33', 'site_logo', NULL, '/logo.svg', '用于显示登录页面的系统LOGO。', NULL, NULL),
('密码是否允许包含正反序帐户名', 'password_contain_name', NULL, '0', '', NULL, NULL),
('密码错误锁定帐户次数', 'password_error_count', NULL, '5', '0表示不限制。', NULL, NULL),
('密码有效期', 'password_expiration_days', NULL, '0', '取值范围为0-999,0表示永久有效。', NULL, NULL),
('密码错误锁定帐户的时间', 'password_lock_minutes', NULL, '5', '0表示不解锁。', NULL, NULL),
('密码最小长度', 'password_min_length', NULL, '8', '取值范围为8-32。', NULL, NULL),
('密码是否必须包含特殊字符', 'password_special_char', NULL, '0', '', NULL, NULL),
('修改密码最短间隔', 'password_update_interval', NULL, '5', '取值范围为0-9999,0表示不限制。', NULL, NULL);
-- 初始化默认字典 -- 初始化默认字典
INSERT INTO `sys_dict` INSERT INTO `sys_dict`

View File

@ -114,7 +114,14 @@ VALUES
'Copyright © 2022-present&nbsp;<a href="https://blog.charles7c.top/about/me" target="_blank" rel="noopener">Charles7c</a>&nbsp;<span>⋅</span>&nbsp;<a href="https://github.com/Charles7c/continew-admin" target="_blank" rel="noopener">ContiNew Admin</a>&nbsp;<span>⋅</span>&nbsp;<a href="https://beian.miit.gov.cn" target="_blank" rel="noopener">津ICP备2022005864号-2</a>', 'Copyright © 2022-present&nbsp;<a href="https://blog.charles7c.top/about/me" target="_blank" rel="noopener">Charles7c</a>&nbsp;<span>⋅</span>&nbsp;<a href="https://github.com/Charles7c/continew-admin" target="_blank" rel="noopener">ContiNew Admin</a>&nbsp;<span>⋅</span>&nbsp;<a href="https://beian.miit.gov.cn" target="_blank" rel="noopener">津ICP备2022005864号-2</a>',
'用于显示登录页面的底部版权信息。', NULL, NULL), '用于显示登录页面的底部版权信息。', NULL, NULL),
('系统LOGO16*16', 'site_favicon', NULL, '/favicon.ico', '用于显示浏览器地址栏的系统LOGO。', NULL, NULL), ('系统LOGO16*16', 'site_favicon', NULL, '/favicon.ico', '用于显示浏览器地址栏的系统LOGO。', NULL, NULL),
('系统LOGO33*33', 'site_logo', NULL, '/logo.svg', '用于显示登录页面的系统LOGO。', NULL, NULL); ('系统LOGO33*33', 'site_logo', NULL, '/logo.svg', '用于显示登录页面的系统LOGO。', NULL, NULL),
('密码是否允许包含正反序帐户名', 'password_contain_name', NULL, '0', '', NULL, NULL),
('密码错误锁定帐户次数', 'password_error_count', NULL, '5', '0表示不限制。', NULL, NULL),
('密码有效期', 'password_expiration_days', NULL, '0', '取值范围为0-999,0表示永久有效。', NULL, NULL),
('密码错误锁定帐户的时间', 'password_lock_minutes', NULL, '5', '0表示不解锁。', NULL, NULL),
('密码最小长度', 'password_min_length', NULL, '8', '取值范围为8-32。', NULL, NULL),
('密码是否必须包含特殊字符', 'password_special_char', NULL, '0', '', NULL, NULL),
('修改密码最短间隔', 'password_update_interval', NULL, '5', '取值范围为0-9999,0表示不限制。', NULL, NULL);
-- 初始化默认字典 -- 初始化默认字典
INSERT INTO "sys_dict" INSERT INTO "sys_dict"
@ -125,8 +132,8 @@ VALUES
INSERT INTO "sys_dict_item" INSERT INTO "sys_dict_item"
("id", "label", "value", "color", "sort", "description", "status", "dict_id", "create_user", "create_time", "update_user", "update_time") ("id", "label", "value", "color", "sort", "description", "status", "dict_id", "create_user", "create_time", "update_user", "update_time")
VALUES VALUES
(547889649658363951, '通知', '1', 'blue', 1, NULL, 547889614262632491, 1, 1, NOW(), NULL, NULL), (547889649658363951, '通知', '1', 'blue', 1, NULL, 1, 547889614262632491, 1, NOW(), NULL, NULL),
(547890124537462835, '活动', '2', 'orangered', 2, NULL, 547889614262632491, 1, 1, NOW(), NULL, NULL); (547890124537462835, '活动', '2', 'orangered', 2, NULL, 1, 547889614262632491, 1, NOW(), NULL, NULL);
-- 初始化默认用户和角色关联数据 -- 初始化默认用户和角色关联数据
INSERT INTO "sys_user_role" INSERT INTO "sys_user_role"

View File

@ -16,13 +16,31 @@
package com.zayac.admin; package com.zayac.admin;
import com.zayac.admin.schedule.CheckRegAndDep;
import com.zayac.admin.system.service.DeptService;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest @SpringBootTest
class ContiNewAdminApplicationTests { class ZayacAdminApplicationTests {
@Autowired
private CheckRegAndDep checkRegAndDep;
@Autowired
private DeptService deptService;
@Test @Test
void contextLoads() { void contextLoads() {
} }
@Test
void testSchedule() {
checkRegAndDep.checkRegistrationAndNewDeposit();
}
@Test
void testQuery() {
var users = deptService.getDeptWithUsersAndAccounts("minister");
System.out.println(users);
}
} }