11 Commits

59 changed files with 343 additions and 112 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 90 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 210 KiB

After

Width:  |  Height:  |  Size: 211 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 234 KiB

After

Width:  |  Height:  |  Size: 253 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 286 KiB

After

Width:  |  Height:  |  Size: 252 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 99 KiB

After

Width:  |  Height:  |  Size: 99 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 113 KiB

After

Width:  |  Height:  |  Size: 113 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 83 KiB

After

Width:  |  Height:  |  Size: 85 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 93 KiB

After

Width:  |  Height:  |  Size: 94 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 90 KiB

After

Width:  |  Height:  |  Size: 91 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 100 KiB

After

Width:  |  Height:  |  Size: 100 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 69 KiB

After

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 87 KiB

After

Width:  |  Height:  |  Size: 87 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 144 KiB

After

Width:  |  Height:  |  Size: 147 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 131 KiB

After

Width:  |  Height:  |  Size: 146 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 87 KiB

After

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 375 KiB

After

Width:  |  Height:  |  Size: 305 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 63 KiB

After

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 88 KiB

After

Width:  |  Height:  |  Size: 89 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 120 KiB

After

Width:  |  Height:  |  Size: 128 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 102 KiB

After

Width:  |  Height:  |  Size: 105 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 849 KiB

After

Width:  |  Height:  |  Size: 808 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 136 KiB

After

Width:  |  Height:  |  Size: 119 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 78 KiB

After

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 96 KiB

After

Width:  |  Height:  |  Size: 96 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 101 KiB

After

Width:  |  Height:  |  Size: 99 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 89 KiB

After

Width:  |  Height:  |  Size: 94 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 167 KiB

After

Width:  |  Height:  |  Size: 168 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 73 KiB

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 122 KiB

After

Width:  |  Height:  |  Size: 122 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 154 KiB

After

Width:  |  Height:  |  Size: 150 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 129 KiB

After

Width:  |  Height:  |  Size: 131 KiB

View File

@@ -1,3 +1,23 @@
## [v3.0.1](https://github.com/Charles7c/continew-admin/compare/v3.0.0...v3.0.1) (2024-05-03)
### ✨ 新特性
* 新增验证码超时显示效果,超时后显示已过期请刷新 (GitHub#56) ([4c6a7fb](https://github.com/Charles7c/continew-admin/commit/4c6a7fb91ad195b86d776f8aef6aef81d07b2eb1))
* 文件管理增加资源统计,统计总存储量、各类型文件存储占用 (GitHub#58) ([15c966f](https://github.com/Charles7c/continew-admin/commit/15c966f7bb255db3edea249f8d3354324cbdbf5b))
### 💎 功能优化
- 获取图片验证码 URL /img => /image ([9a1a472](https://github.com/Charles7c/continew-admin/commit/9a1a472ec996362cb918e79b9ce37bfa2639a10b))
- 移除对部分 API 重复的权限校验 ([53eaef9](https://github.com/Charles7c/continew-admin/commit/53eaef9fbdfd6d0866a3d5e424d783e2e7bc0e17))
- 优化代码生成模板 ([dc92731](https://github.com/Charles7c/continew-admin/commit/dc9273132dc8e266f2d44c834b9c2733256afdfe)) ([def831f](https://github.com/Charles7c/continew-admin/commit/def831f2dca0703f5ef8b84b0e695a32b171461d))
### 🐛 问题修复
- 修复查询用户邮箱、手机号时未自动加密导致的错误 ([faa56d1](https://github.com/Charles7c/continew-admin/commit/faa56d16b92cbdb8f7e16c8b43c2916ae692d881))
- 修复根据部门查询用户列表数据错误 ([42ac82e](https://github.com/Charles7c/continew-admin/commit/42ac82e7ceef9336741c2514470c0db36ab7075e))
- 修复文件类型处理错误 ([9b60e24](https://github.com/Charles7c/continew-admin/commit/9b60e24364bfb4cc7cd9996a43579a062197cdf3))
## [v3.0.0](https://github.com/Charles7c/continew-admin/compare/v2.5.0...v3.0.0) (2024-04-27)
### ✨ 新特性

View File

@@ -4,7 +4,7 @@
<img src="https://img.shields.io/badge/License-Apache--2.0-blue.svg" alt="License" />
</a>
<a href="https://github.com/Charles7c/continew-admin" target="_blank">
<img src="https://img.shields.io/badge/RELEASE-v3.0.0-%23ff3f59.svg" alt="Release" />
<img src="https://img.shields.io/badge/RELEASE-v3.0.1-%23ff3f59.svg" alt="Release" />
</a>
<a href="https://app.codacy.com/gh/Charles7c/continew-admin/dashboard?utm_source=gh&utm_medium=referral&utm_content=&utm_campaign=Badge_grade" target="_blank">
<img src="https://app.codacy.com/project/badge/Grade/19e3e2395d554efe902c3822e65db30e" alt="Codacy Badge" />
@@ -130,7 +130,7 @@ public class DeptController extends BaseController<DeptService, DeptResp, DeptDe
## 系统截图
> [!TIP]
> 受篇幅长度及功能更新频率影响,下方仅为系统 **部分** 功能于 **2024年4月27日** 进行的截图,更多新增功能及细节请登录演示环境或 clone 代码到本地启动查看。
> 受篇幅长度及功能更新频率影响,下方仅为系统 **部分** 功能于 **2024年5月3日** 进行的截图,更多新增功能及细节请登录演示环境或 clone 代码到本地启动查看。
<table border="1" cellpadding="1" cellspacing="1" style="width: 500px">
<tbody>

View File

@@ -48,4 +48,10 @@ public class CaptchaResp implements Serializable {
*/
@Schema(description = "验证码图片Base64编码带图片格式data:image/gif;base64", example = "data:image/png;base64,iVBORw0KGgoAAAAN...")
private String img;
/**
* 过期时间戳
*/
@Schema(description = "过期时间戳", example = "1714376969409")
private Long expireTime;
}

View File

@@ -22,7 +22,7 @@ export interface ${classNamePrefix}DetailResp {
updateUserString: string
</#if>
}
export interface ${classNamePrefix}Query extends PageQuery {
export interface ${classNamePrefix}Query {
<#if fieldConfigs??>
<#list fieldConfigs as fieldConfig>
<#if fieldConfig.showInQuery>
@@ -30,10 +30,12 @@ export interface ${classNamePrefix}Query extends PageQuery {
</#if>
</#list>
</#if>
sort: Array<string>
}
export interface ${classNamePrefix}PageQuery extends ${classNamePrefix}Query, PageQuery {}
/** @desc 查询${businessName}列表 */
export function list${classNamePrefix}(query: ${classNamePrefix}Query) {
export function list${classNamePrefix}(query: ${classNamePrefix}PageQuery) {
return http.get<PageRes<${classNamePrefix}Resp[]>>(`${'$'}{BASE_URL}`, query)
}

View File

@@ -28,7 +28,7 @@
<span>新增</span>
</a-button>
<a-tooltip content="导出">
<a-button v-permission="['${apiModuleName}:${apiName}:export']" @click="onExport">
<a-button v-permission="['${apiModuleName}:${apiName}:export']" class="gi_hover_btn-border" @click="onExport">
<template #icon>
<icon-download />
</template>
@@ -74,7 +74,7 @@ const columns: TableInstanceColumns[] = [
<#if fieldConfigs??>
<#list fieldConfigs as fieldConfig>
<#if fieldConfig.showInList>
{ title: '${fieldConfig.comment}', dataIndex: '${fieldConfig.fieldName}' },
{ title: '${fieldConfig.comment}', dataIndex: '${fieldConfig.fieldName}', slotName: ${fieldConfig.fieldName} },
</#if>
</#list>
</#if>
@@ -88,7 +88,7 @@ const columns: TableInstanceColumns[] = [
}
]
const queryForm: ${classNamePrefix}Query = reactive({
const queryForm = reactive<${classNamePrefix}Query>({
<#list fieldConfigs as fieldConfig>
<#if fieldConfig.showInQuery>
${fieldConfig.fieldName}: undefined,

View File

@@ -43,7 +43,7 @@ public enum FileTypeEnum implements IBaseEnum<Integer> {
/**
* 图片
*/
IMAGE(2, "图片", List.of("jpg", "png", "gif", "bmp", "webp", "ico", "psd", "tiff", "dwg", "jxr", "apng", "xcf")),
IMAGE(2, "图片", List.of("jpg", "jpeg", "png", "gif", "bmp", "webp", "ico", "psd", "tiff", "dwg", "jxr", "apng", "xcf")),
/**
* 文档

View File

@@ -16,9 +16,13 @@
package top.continew.admin.system.mapper;
import org.apache.ibatis.annotations.Select;
import top.continew.admin.system.model.entity.FileDO;
import top.continew.admin.system.model.resp.FileStatisticsResp;
import top.continew.starter.data.mybatis.plus.base.BaseMapper;
import java.util.List;
/**
* 文件 Mapper
*
@@ -26,4 +30,12 @@ import top.continew.starter.data.mybatis.plus.base.BaseMapper;
* @since 2023/12/23 10:38
*/
public interface FileMapper extends BaseMapper<FileDO> {
/**
* 查询文件资源统计信息
*
* @return 文件资源统计信息
*/
@Select("SELECT type, COUNT(1) number, SUM(size) size FROM sys_file GROUP BY type")
List<FileStatisticsResp> statistics();
}

View File

@@ -16,10 +16,14 @@
package top.continew.admin.system.mapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Constants;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import top.continew.admin.common.config.mybatis.DataPermissionMapper;
import top.continew.admin.system.model.entity.UserDO;
import top.continew.starter.data.mybatis.plus.datapermission.DataPermission;
import top.continew.starter.security.crypto.annotation.FieldEncrypt;
/**
@@ -30,6 +34,17 @@ import top.continew.starter.security.crypto.annotation.FieldEncrypt;
*/
public interface UserMapper extends DataPermissionMapper<UserDO> {
/**
* 分页查询列表
*
* @param page 分页条件
* @param queryWrapper 查询条件
* @return 分页列表信息
*/
@DataPermission
IPage<UserDO> selectUserPage(@Param("page") IPage<UserDO> page,
@Param(Constants.WRAPPER) QueryWrapper<UserDO> queryWrapper);
/**
* 根据用户名查询
*
@@ -65,4 +80,20 @@ public interface UserMapper extends DataPermissionMapper<UserDO> {
*/
@Select("SELECT nickname FROM sys_user WHERE id = #{id}")
String selectNicknameById(@Param("id") Long id);
/**
* 根据邮箱查询数量
*
* @param email 邮箱
* @return 用户数量
*/
Long selectCountByEmail(@FieldEncrypt @Param("email") String email, @Param("id") Long id);
/**
* 根据手机号查询数量
*
* @param phone 手机号
* @return 用户数量
*/
Long selectCountByPhone(@FieldEncrypt @Param("phone") String phone, @Param("id") Long id);
}

View File

@@ -18,6 +18,7 @@ package top.continew.admin.system.model.query;
import cn.hutool.core.date.DatePattern;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.Size;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
@@ -68,6 +69,7 @@ public class LogQuery implements Serializable {
*/
@Schema(description = "操作时间", example = "2023-08-08 00:00:00,2023-08-08 23:59:59")
@DateTimeFormat(pattern = DatePattern.NORM_DATETIME_PATTERN)
@Size(max = 2, message = "操作时间必须是一个范围")
private List<Date> createTime;
/**

View File

@@ -18,10 +18,9 @@ package top.continew.admin.system.model.query;
import cn.hutool.core.date.DatePattern;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.Size;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import top.continew.starter.data.core.annotation.Query;
import top.continew.starter.data.core.enums.QueryType;
import java.io.Serial;
import java.io.Serializable;
@@ -45,7 +44,6 @@ public class UserQuery implements Serializable {
* 关键词
*/
@Schema(description = "关键词", example = "zhangsan")
@Query(columns = {"username", "nickname", "description"}, type = QueryType.LIKE)
private String description;
/**
@@ -58,8 +56,8 @@ public class UserQuery implements Serializable {
* 创建时间
*/
@Schema(description = "创建时间", example = "2023-08-08 00:00:00,2023-08-08 23:59:59")
@Query(type = QueryType.BETWEEN)
@DateTimeFormat(pattern = DatePattern.NORM_DATETIME_PATTERN)
@Size(max = 2, message = "创建时间必须是一个范围")
private List<Date> createTime;
/**

View File

@@ -26,7 +26,6 @@ import org.hibernate.validator.constraints.Length;
import top.continew.admin.common.constant.RegexConstants;
import top.continew.admin.common.enums.DisEnableStatusEnum;
import top.continew.starter.extension.crud.model.req.BaseReq;
import top.continew.starter.extension.crud.util.ValidateGroup;
import java.io.Serial;
@@ -47,7 +46,7 @@ public class DeptReq extends BaseReq {
* 上级部门 ID
*/
@Schema(description = "上级部门 ID", example = "2")
@NotNull(message = "上级部门不能为空", groups = ValidateGroup.Crud.Add.class)
@NotNull(message = "上级部门不能为空")
private Long parentId;
/**

View File

@@ -0,0 +1,66 @@
/*
* 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 top.continew.admin.system.model.resp;
import com.fasterxml.jackson.annotation.JsonInclude;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import top.continew.admin.system.enums.FileTypeEnum;
import java.io.Serial;
import java.io.Serializable;
import java.util.List;
/**
* 文件资源统计信息
*
* @author Kils
* @since 2024/4/30 14:30
*/
@Data
@Schema(description = "文件资源统计信息")
@JsonInclude(JsonInclude.Include.NON_NULL)
public class FileStatisticsResp implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 文件类型
*/
@Schema(description = "类型1其他2图片3文档4视频5音频", type = "Integer", allowableValues = {"1", "2", "3", "4",
"5"}, example = "2")
private FileTypeEnum type;
/**
* 大小(字节)
*/
@Schema(description = "大小(字节)", example = "4096")
private Long size;
/**
* 数量
*/
@Schema(description = "数量", example = "1000")
private Long number;
/**
* 分类数据
*/
@Schema(description = "分类数据")
private List<FileStatisticsResp> data;
}

View File

@@ -23,6 +23,8 @@ import top.continew.admin.system.model.resp.DeptResp;
import top.continew.starter.data.mybatis.plus.service.IService;
import top.continew.starter.extension.crud.service.BaseService;
import java.util.List;
/**
* 部门业务接口
*
@@ -30,4 +32,12 @@ import top.continew.starter.extension.crud.service.BaseService;
* @since 2023/1/22 17:54
*/
public interface DeptService extends BaseService<DeptResp, DeptResp, DeptQuery, DeptReq>, IService<DeptDO> {
/**
* 查询子部门列表
*
* @param id ID
* @return 子部门列表
*/
List<DeptDO> listChildren(Long id);
}

View File

@@ -22,6 +22,7 @@ import top.continew.admin.system.model.entity.FileDO;
import top.continew.admin.system.model.query.FileQuery;
import top.continew.admin.system.model.req.FileReq;
import top.continew.admin.system.model.resp.FileResp;
import top.continew.admin.system.model.resp.FileStatisticsResp;
import top.continew.starter.data.mybatis.plus.service.IService;
import top.continew.starter.extension.crud.service.BaseService;
@@ -61,4 +62,11 @@ public interface FileService extends BaseService<FileResp, FileResp, FileQuery,
* @return 文件数量
*/
Long countByStorageIds(List<Long> storageIds);
/**
* 查询文件资源统计信息
*
* @return 资源统计信息
*/
FileStatisticsResp statistics();
}

View File

@@ -20,6 +20,7 @@ import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.extra.spring.SpringUtil;
import com.baomidou.dynamic.datasource.DynamicRoutingDataSource;
import jakarta.annotation.Resource;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import top.continew.admin.common.enums.DisEnableStatusEnum;
@@ -50,9 +51,17 @@ import java.util.Optional;
@RequiredArgsConstructor
public class DeptServiceImpl extends BaseServiceImpl<DeptMapper, DeptDO, DeptResp, DeptResp, DeptQuery, DeptReq> implements DeptService {
private final UserService userService;
@Resource
private UserService userService;
private final RoleDeptService roleDeptService;
@Override
public List<DeptDO> listChildren(Long id) {
DatabaseType databaseType = MetaUtils.getDatabaseTypeOrDefault(SpringUtil
.getBean(DynamicRoutingDataSource.class), DatabaseType.MYSQL);
return baseMapper.lambdaQuery().apply(databaseType.findInSet(id, "ancestors")).list();
}
@Override
protected void beforeAdd(DeptReq req) {
String name = req.getName();
@@ -150,18 +159,6 @@ public class DeptServiceImpl extends BaseServiceImpl<DeptMapper, DeptDO, DeptRes
return parentDept;
}
/**
* 查询子部门列表
*
* @param id ID
* @return 子部门列表
*/
private List<DeptDO> listChildren(Long id) {
DatabaseType databaseType = MetaUtils.getDatabaseTypeOrDefault(SpringUtil
.getBean(DynamicRoutingDataSource.class), DatabaseType.MYSQL);
return baseMapper.lambdaQuery().apply(databaseType.findInSet(id, "ancestors")).list();
}
/**
* 查询子部门数量
*

View File

@@ -16,6 +16,7 @@
package top.continew.admin.system.service.impl;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.core.util.URLUtil;
import jakarta.annotation.Resource;
@@ -34,6 +35,7 @@ import top.continew.admin.system.model.entity.StorageDO;
import top.continew.admin.system.model.query.FileQuery;
import top.continew.admin.system.model.req.FileReq;
import top.continew.admin.system.model.resp.FileResp;
import top.continew.admin.system.model.resp.FileStatisticsResp;
import top.continew.admin.system.service.FileService;
import top.continew.admin.system.service.StorageService;
import top.continew.starter.core.constant.StringConstants;
@@ -113,6 +115,19 @@ public class FileServiceImpl extends BaseServiceImpl<FileMapper, FileDO, FileRes
return baseMapper.lambdaQuery().in(FileDO::getStorageId, storageIds).count();
}
@Override
public FileStatisticsResp statistics() {
FileStatisticsResp resp = new FileStatisticsResp();
List<FileStatisticsResp> statisticsList = baseMapper.statistics();
if (CollUtil.isEmpty(statisticsList)) {
return resp;
}
resp.setData(statisticsList);
resp.setSize(statisticsList.stream().mapToLong(FileStatisticsResp::getSize).sum());
resp.setNumber(statisticsList.stream().mapToLong(FileStatisticsResp::getNumber).sum());
return resp;
}
@Override
protected void fill(Object obj) {
super.fill(obj);

View File

@@ -41,7 +41,6 @@ import top.continew.admin.system.model.resp.log.LoginLogExportResp;
import top.continew.admin.system.model.resp.log.OperationLogExportResp;
import top.continew.admin.system.service.LogService;
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.SortQuery;
import top.continew.starter.extension.crud.model.resp.PageResp;
@@ -66,7 +65,7 @@ public class LogServiceImpl implements LogService {
@Override
public PageResp<LogResp> page(LogQuery query, PageQuery pageQuery) {
QueryWrapper<LogDO> queryWrapper = this.handleQueryWrapper(query);
QueryWrapper<LogDO> queryWrapper = this.buildQueryWrapper(query);
IPage<LogResp> page = baseMapper.selectLogPage(pageQuery.toPage(), queryWrapper);
return PageResp.build(page);
}
@@ -120,7 +119,7 @@ public class LogServiceImpl implements LogService {
* @return 列表信息
*/
private List<LogResp> list(LogQuery query, SortQuery sortQuery) {
QueryWrapper<LogDO> queryWrapper = this.handleQueryWrapper(query);
QueryWrapper<LogDO> queryWrapper = this.buildQueryWrapper(query);
this.sort(queryWrapper, sortQuery);
return baseMapper.selectLogList(queryWrapper);
}
@@ -142,39 +141,28 @@ public class LogServiceImpl implements LogService {
}
/**
* 处理查询条件
* 构建 QueryWrapper
*
* @param query 查询条件
* @return QueryWrapper
*/
private QueryWrapper<LogDO> handleQueryWrapper(LogQuery query) {
QueryWrapper<LogDO> queryWrapper = new QueryWrapper<>();
// 构建条件
private QueryWrapper<LogDO> buildQueryWrapper(LogQuery query) {
String description = query.getDescription();
if (StrUtil.isNotBlank(description)) {
queryWrapper.and(q -> q.like("t1.description", description).or().like("t1.module", description));
}
String module = query.getModule();
if (StrUtil.isNotBlank(module)) {
queryWrapper.eq("t1.module", module);
}
String ip = query.getIp();
if (StrUtil.isNotBlank(ip)) {
queryWrapper.and(q -> q.like("t1.ip", ip).or().like("t1.address", ip));
}
String createUserString = query.getCreateUserString();
if (StrUtil.isNotBlank(createUserString)) {
queryWrapper.and(q -> q.like("t2.username", createUserString).or().like("t2.nickname", createUserString));
}
List<Date> createTimeList = query.getCreateTime();
if (CollUtil.isNotEmpty(createTimeList)) {
ValidationUtils.throwIf(createTimeList.size() != 2, "[{}] 必须是一个范围", "createTime");
queryWrapper.between("t1.create_time", createTimeList.get(0), createTimeList.get(1));
}
Integer status = query.getStatus();
if (null != status) {
queryWrapper.eq("t1.status", status);
}
return queryWrapper;
List<Date> createTimeList = query.getCreateTime();
return new QueryWrapper<LogDO>().and(StrUtil.isNotBlank(description), q -> q.like("t1.description", description)
.or()
.like("t1.module", description))
.eq(StrUtil.isNotBlank(module), "t1.module", module)
.and(StrUtil.isNotBlank(ip), q -> q.like("t1.ip", ip).or().like("t1.address", ip))
.and(StrUtil.isNotBlank(createUserString), q -> q.like("t2.username", createUserString)
.or()
.like("t2.nickname", createUserString))
.eq(null != status, "t1.status", status)
.between(CollUtil.isNotEmpty(createTimeList), "t1.create_time", CollUtil.getFirst(createTimeList), CollUtil
.getLast(createTimeList));
}
}

View File

@@ -25,6 +25,9 @@ import com.alicp.jetcache.anno.CacheInvalidate;
import com.alicp.jetcache.anno.CacheType;
import com.alicp.jetcache.anno.CacheUpdate;
import com.alicp.jetcache.anno.Cached;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import jakarta.annotation.Resource;
import lombok.RequiredArgsConstructor;
import org.dromara.x.file.storage.core.FileInfo;
import org.dromara.x.file.storage.core.FileStorageService;
@@ -38,6 +41,7 @@ import top.continew.admin.common.constant.CacheConstants;
import top.continew.admin.common.enums.DisEnableStatusEnum;
import top.continew.admin.common.util.helper.LoginHelper;
import top.continew.admin.system.mapper.UserMapper;
import top.continew.admin.system.model.entity.DeptDO;
import top.continew.admin.system.model.entity.UserDO;
import top.continew.admin.system.model.query.UserQuery;
import top.continew.admin.system.model.req.UserBasicInfoUpdateReq;
@@ -46,19 +50,20 @@ import top.continew.admin.system.model.req.UserReq;
import top.continew.admin.system.model.req.UserRoleUpdateReq;
import top.continew.admin.system.model.resp.UserDetailResp;
import top.continew.admin.system.model.resp.UserResp;
import top.continew.admin.system.service.FileService;
import top.continew.admin.system.service.RoleService;
import top.continew.admin.system.service.UserRoleService;
import top.continew.admin.system.service.UserService;
import top.continew.admin.system.service.*;
import top.continew.starter.core.constant.StringConstants;
import top.continew.starter.core.util.validate.CheckUtils;
import top.continew.starter.extension.crud.model.query.PageQuery;
import top.continew.starter.extension.crud.model.resp.PageResp;
import top.continew.starter.extension.crud.service.CommonUserService;
import top.continew.starter.extension.crud.service.impl.BaseServiceImpl;
import java.time.LocalDateTime;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
/**
* 用户业务实现
@@ -76,9 +81,20 @@ public class UserServiceImpl extends BaseServiceImpl<UserMapper, UserDO, UserRes
private final FileService fileService;
private final FileStorageService fileStorageService;
private final PasswordEncoder passwordEncoder;
@Resource
private DeptService deptService;
@Value("${avatar.support-suffix}")
private String[] avatarSupportSuffix;
@Override
public PageResp<UserResp> page(UserQuery query, PageQuery pageQuery) {
QueryWrapper<UserDO> queryWrapper = this.buildQueryWrapper(query);
IPage<UserDO> page = baseMapper.selectUserPage(pageQuery.toPage(), queryWrapper);
PageResp<UserResp> pageResp = PageResp.build(page, this.listClass);
pageResp.getList().forEach(this::fill);
return pageResp;
}
@Override
public Long add(UserDO user) {
user.setStatus(DisEnableStatusEnum.ENABLE);
@@ -86,25 +102,6 @@ public class UserServiceImpl extends BaseServiceImpl<UserMapper, UserDO, UserRes
return user.getId();
}
@Override
protected void beforeAdd(UserReq req) {
final String errorMsgTemplate = "新增失败,[{}] 已存在";
String username = req.getUsername();
CheckUtils.throwIf(this.isNameExists(username, null), errorMsgTemplate, username);
String email = req.getEmail();
CheckUtils.throwIf(StrUtil.isNotBlank(email) && this.isEmailExists(email, null), errorMsgTemplate, email);
String phone = req.getPhone();
CheckUtils.throwIf(StrUtil.isNotBlank(phone) && this.isPhoneExists(phone, null), errorMsgTemplate, phone);
}
@Override
protected void afterAdd(UserReq req, UserDO user) {
Long userId = user.getId();
baseMapper.lambdaUpdate().set(UserDO::getPwdResetTime, LocalDateTime.now()).eq(UserDO::getId, userId).update();
// 保存用户和角色关联
userRoleService.add(req.getRoleIds(), userId);
}
@Override
@Transactional(rollbackFor = Exception.class)
@CacheUpdate(key = "#id", value = "#req.nickname", name = CacheConstants.USER_KEY_PREFIX)
@@ -157,17 +154,6 @@ public class UserServiceImpl extends BaseServiceImpl<UserMapper, UserDO, UserRes
super.delete(ids);
}
@Override
protected void fill(Object obj) {
super.fill(obj);
if (obj instanceof UserDetailResp detail) {
List<Long> roleIdList = detail.getRoleIds();
if (CollUtil.isNotEmpty(roleIdList)) {
detail.setRoleNames(String.join(StringConstants.CHINESE_COMMA, roleService.listNameByIds(roleIdList)));
}
}
}
@Override
@Transactional(rollbackFor = Exception.class)
public String uploadAvatar(MultipartFile avatarFile, Long id) {
@@ -217,8 +203,7 @@ public class UserServiceImpl extends BaseServiceImpl<UserMapper, UserDO, UserRes
public void updatePhone(String newPhone, String currentPassword, Long id) {
UserDO user = super.getById(id);
CheckUtils.throwIf(!passwordEncoder.matches(currentPassword, user.getPassword()), "当前密码错误");
Long count = baseMapper.lambdaQuery().eq(UserDO::getPhone, newPhone).count();
CheckUtils.throwIf(count > 0, "手机号已绑定其他账号,请更换其他手机号");
CheckUtils.throwIf(this.isPhoneExists(newPhone, id), "手机号已绑定其他账号,请更换其他手机号");
CheckUtils.throwIfEqual(newPhone, user.getPhone(), "新手机号不能与当前手机号相同");
// 更新手机号
baseMapper.lambdaUpdate().set(UserDO::getPhone, newPhone).eq(UserDO::getId, id).update();
@@ -228,8 +213,7 @@ public class UserServiceImpl extends BaseServiceImpl<UserMapper, UserDO, UserRes
public void updateEmail(String newEmail, String currentPassword, Long id) {
UserDO user = super.getById(id);
CheckUtils.throwIf(!passwordEncoder.matches(currentPassword, user.getPassword()), "当前密码错误");
Long count = baseMapper.lambdaQuery().eq(UserDO::getEmail, newEmail).count();
CheckUtils.throwIf(count > 0, "邮箱已绑定其他账号,请更换其他邮箱");
CheckUtils.throwIf(this.isEmailExists(newEmail, id), "邮箱已绑定其他账号,请更换其他邮箱");
CheckUtils.throwIfEqual(newEmail, user.getEmail(), "新邮箱不能与当前邮箱相同");
// 更新邮箱
baseMapper.lambdaUpdate().set(UserDO::getEmail, newEmail).eq(UserDO::getId, id).update();
@@ -277,6 +261,65 @@ public class UserServiceImpl extends BaseServiceImpl<UserMapper, UserDO, UserRes
return baseMapper.selectNicknameById(id);
}
@Override
protected void fill(Object obj) {
super.fill(obj);
if (obj instanceof UserDetailResp detail) {
List<Long> roleIdList = detail.getRoleIds();
if (CollUtil.isNotEmpty(roleIdList)) {
detail.setRoleNames(String.join(StringConstants.CHINESE_COMMA, roleService.listNameByIds(roleIdList)));
}
}
}
@Override
protected void beforeAdd(UserReq req) {
final String errorMsgTemplate = "新增失败,[{}] 已存在";
String username = req.getUsername();
CheckUtils.throwIf(this.isNameExists(username, null), errorMsgTemplate, username);
String email = req.getEmail();
CheckUtils.throwIf(StrUtil.isNotBlank(email) && this.isEmailExists(email, null), errorMsgTemplate, email);
String phone = req.getPhone();
CheckUtils.throwIf(StrUtil.isNotBlank(phone) && this.isPhoneExists(phone, null), errorMsgTemplate, phone);
}
@Override
protected void afterAdd(UserReq req, UserDO user) {
Long userId = user.getId();
baseMapper.lambdaUpdate().set(UserDO::getPwdResetTime, LocalDateTime.now()).eq(UserDO::getId, userId).update();
// 保存用户和角色关联
userRoleService.add(req.getRoleIds(), userId);
}
/**
* 构建 QueryWrapper
*
* @param query 查询条件
* @return QueryWrapper
*/
private QueryWrapper<UserDO> buildQueryWrapper(UserQuery query) {
String description = query.getDescription();
Integer status = query.getStatus();
List<Date> createTimeList = query.getCreateTime();
Long deptId = query.getDeptId();
return new QueryWrapper<UserDO>().and(StrUtil.isNotBlank(description), q -> q.like("t1.username", description)
.or()
.like("t1.nickname", description)
.or()
.like("t1.description", description))
.eq(null != status, "t1.status", status)
.between(CollUtil.isNotEmpty(createTimeList), "t1.create_time", CollUtil.getFirst(createTimeList), CollUtil
.getLast(createTimeList))
.and(null != deptId, q -> {
List<Long> deptIdList = deptService.listChildren(deptId)
.stream()
.map(DeptDO::getId)
.collect(Collectors.toList());
deptIdList.add(deptId);
q.in("t1.dept_id", deptIdList);
});
}
/**
* 名称是否存在
*
@@ -296,7 +339,8 @@ public class UserServiceImpl extends BaseServiceImpl<UserMapper, UserDO, UserRes
* @return 是否存在
*/
private boolean isEmailExists(String email, Long id) {
return baseMapper.lambdaQuery().eq(UserDO::getEmail, email).ne(null != id, UserDO::getId, id).exists();
Long count = baseMapper.selectCountByEmail(email, id);
return null != count && count > 0;
}
/**
@@ -307,6 +351,7 @@ public class UserServiceImpl extends BaseServiceImpl<UserMapper, UserDO, UserRes
* @return 是否存在
*/
private boolean isPhoneExists(String phone, Long id) {
return baseMapper.lambdaQuery().eq(UserDO::getPhone, phone).ne(null != id, UserDO::getId, id).exists();
Long count = baseMapper.selectCountByPhone(phone, id);
return null != count && count > 0;
}
}

View File

@@ -1,4 +1,29 @@
<?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="top.continew.admin.system.mapper.UserMapper">
<select id="selectUserPage" resultType="top.continew.admin.system.model.entity.UserDO">
SELECT t1.*
FROM sys_user AS t1
LEFT JOIN sys_dept AS t2 ON t2.id = t1.dept_id
${ew.customSqlSegment}
</select>
<select id="selectCountByEmail" resultType="java.lang.Long">
SELECT count(*)
FROM sys_user
WHERE email = #{email}
<if test="id != null">
AND id != #{id}
</if>
</select>
<select id="selectCountByPhone" resultType="java.lang.Long">
SELECT count(*)
FROM sys_user
WHERE phone = #{phone}
<if test="id != null">
AND id != #{id}
</if>
</select>
</mapper>

View File

@@ -17,6 +17,7 @@
package top.continew.admin.webapi.common;
import cn.dev33.satoken.annotation.SaIgnore;
import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.core.lang.Dict;
import cn.hutool.core.lang.RegexPool;
import cn.hutool.core.map.MapUtil;
@@ -57,6 +58,7 @@ import top.continew.starter.messaging.mail.util.MailUtils;
import top.continew.starter.web.model.R;
import java.time.Duration;
import java.time.LocalDateTime;
import java.util.LinkedHashMap;
import java.util.Map;
@@ -96,13 +98,15 @@ public class CaptchaController {
@Log(ignore = true)
@Operation(summary = "获取图片验证码", description = "获取图片验证码Base64编码带图片格式data:image/gif;base64")
@GetMapping("/img")
@GetMapping("/image")
public R<CaptchaResp> getImageCaptcha() {
String uuid = IdUtil.fastUUID();
String captchaKey = CacheConstants.CAPTCHA_KEY_PREFIX + uuid;
Captcha captcha = graphicCaptchaService.getCaptcha();
long expireTime = LocalDateTimeUtil.toEpochMilli(LocalDateTime.now()
.plusMinutes(captchaProperties.getExpirationInMinutes()));
RedisUtils.set(captchaKey, captcha.text(), Duration.ofMinutes(captchaProperties.getExpirationInMinutes()));
return R.ok(CaptchaResp.builder().uuid(uuid).img(captcha.toBase64()).build());
return R.ok(CaptchaResp.builder().uuid(uuid).img(captcha.toBase64()).expireTime(expireTime).build());
}
@Operation(summary = "获取邮箱验证码", description = "发送验证码到指定邮箱")

View File

@@ -16,17 +16,20 @@
package top.continew.admin.webapi.system;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
import top.continew.admin.system.model.query.FileQuery;
import top.continew.admin.system.model.req.FileReq;
import top.continew.admin.system.model.resp.FileResp;
import top.continew.admin.system.model.resp.FileStatisticsResp;
import top.continew.admin.system.service.FileService;
import top.continew.starter.extension.crud.annotation.CrudRequestMapping;
import top.continew.starter.extension.crud.controller.BaseController;
import top.continew.starter.extension.crud.enums.Api;
import top.continew.starter.log.core.annotation.Log;
import top.continew.starter.web.model.R;
/**
* 文件管理 API
@@ -36,6 +39,16 @@ import top.continew.starter.extension.crud.enums.Api;
*/
@Tag(name = "文件管理 API")
@RestController
@RequiredArgsConstructor
@CrudRequestMapping(value = "/system/file", api = {Api.PAGE, Api.UPDATE, Api.DELETE})
public class FileController extends BaseController<FileService, FileResp, FileResp, FileQuery, FileReq> {
private final FileService fileService;
@Log(ignore = true)
@Operation(summary = "查询文件资源统计", description = "查询文件资源统计")
@GetMapping("/statistics")
public R<FileStatisticsResp> statistics() {
return R.ok(fileService.statistics());
}
}

View File

@@ -16,7 +16,6 @@
package top.continew.admin.webapi.system;
import cn.dev33.satoken.annotation.SaCheckPermission;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import io.swagger.v3.oas.annotations.tags.Tag;
@@ -49,14 +48,12 @@ import top.continew.starter.web.model.R;
public class MenuController extends BaseController<MenuService, MenuResp, MenuResp, MenuQuery, MenuReq> {
@Override
@SaCheckPermission("system:menu:add")
public R<Long> add(@Validated(ValidateGroup.Crud.Add.class) @RequestBody MenuReq req) {
this.checkPath(req);
return super.add(req);
}
@Override
@SaCheckPermission("system:menu:update")
public R<Void> update(@Validated(ValidateGroup.Crud.Update.class) @RequestBody MenuReq req, @PathVariable Long id) {
this.checkPath(req);
return super.update(req, id);

View File

@@ -16,7 +16,6 @@
package top.continew.admin.webapi.system;
import cn.dev33.satoken.annotation.SaCheckPermission;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PathVariable;
@@ -48,14 +47,12 @@ import java.time.LocalDateTime;
public class NoticeController extends BaseController<NoticeService, NoticeResp, NoticeDetailResp, NoticeQuery, NoticeReq> {
@Override
@SaCheckPermission("system:notice:add")
public R<Long> add(@Validated(ValidateGroup.Crud.Add.class) @RequestBody NoticeReq req) {
this.checkTime(req);
return super.add(req);
}
@Override
@SaCheckPermission("system:notice:update")
public R<Void> update(@Validated(ValidateGroup.Crud.Update.class) @RequestBody NoticeReq req,
@PathVariable Long id) {
this.checkTime(req);

View File

@@ -57,15 +57,13 @@ import top.continew.starter.web.model.R;
public class UserController extends BaseController<UserService, UserResp, UserDetailResp, UserQuery, UserReq> {
@Override
@SaCheckPermission("system:user:add")
public R<Long> add(@Validated(ValidateGroup.Crud.Add.class) @RequestBody UserReq req) {
String rawPassword = ExceptionUtils.exToNull(() -> SecureUtils.decryptByRsaPrivateKey(req.getPassword()));
ValidationUtils.throwIfNull(rawPassword, "密码解密失败");
ValidationUtils.throwIf(!ReUtil
.isMatch(RegexConstants.PASSWORD, rawPassword), "密码长度为 6 到 32 位,可以包含字母、数字、下划线,特殊字符,同时包含字母和数字");
req.setPassword(rawPassword);
Long id = baseService.add(req);
return R.ok("新增成功", id);
return super.add(req);
}
@Operation(summary = "重置密码", description = "重置用户登录密码")

View File

@@ -33,7 +33,6 @@ import top.continew.admin.generator.model.req.GenConfigReq;
import top.continew.admin.generator.model.resp.GeneratePreviewResp;
import top.continew.admin.generator.model.resp.TableResp;
import top.continew.admin.generator.service.GeneratorService;
import top.continew.starter.core.autoconfigure.project.ProjectProperties;
import top.continew.starter.extension.crud.model.query.PageQuery;
import top.continew.starter.extension.crud.model.resp.PageResp;
import top.continew.starter.web.model.R;
@@ -54,7 +53,6 @@ import java.util.List;
@RequestMapping("/generator")
public class GeneratorController {
private final ProjectProperties projectProperties;
private final GeneratorService generatorService;
@Operation(summary = "分页查询数据表", description = "分页查询数据表")

View File

@@ -5,7 +5,7 @@ project:
# 应用名称
app-name: continew-admin
# 版本
version: 3.0.0
version: 3.0.1
# 描述
description: 持续迭代优化的前后端分离中后台管理系统框架,开箱即用,持续提供舒适的开发体验。
# 基本包

View File

@@ -31,7 +31,7 @@
<properties>
<!-- 项目版本号 -->
<revision>3.0.0</revision>
<revision>3.0.1</revision>
</properties>
<!-- 全局依赖版本管理 -->