瀏覽代碼

myBatisPlus框架调整

廖泽勇 3 天之前
父節點
當前提交
81740b5e97

+ 13 - 1
pom.xml

@@ -85,7 +85,7 @@
 		<dependency>
 			<groupId>com.baomidou</groupId>
 			<artifactId>mybatis-plus-boot-starter</artifactId>
-			<version>3.0.7.1</version>
+			<version>3.5.2</version>
 		</dependency>
 		<dependency>
 			<groupId>org.springframework.boot</groupId>
@@ -136,6 +136,18 @@
 			<version>3.12.0</version>
 		</dependency>
 
+		<dependency>
+			<groupId>com.sun.mail</groupId>
+			<artifactId>jakarta.mail</artifactId>
+			<version>1.6.5</version>
+		</dependency>
+
+		<dependency>
+			<groupId>cn.hutool</groupId>
+			<artifactId>hutool-all</artifactId>
+			<version>5.7.17</version>
+		</dependency>
+
 	</dependencies>
 
 	<build>

+ 0 - 116
src/main/java/com/jsh/erp/config/TenantConfig.java

@@ -1,116 +0,0 @@
-package com.jsh.erp.config;
-
-import com.baomidou.mybatisplus.core.parser.ISqlParser;
-import com.baomidou.mybatisplus.core.parser.ISqlParserFilter;
-import com.baomidou.mybatisplus.core.parser.SqlParserHelper;
-import com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor;
-import com.baomidou.mybatisplus.extension.plugins.tenant.TenantHandler;
-import com.baomidou.mybatisplus.extension.plugins.tenant.TenantSqlParser;
-import com.jsh.erp.utils.Tools;
-import net.sf.jsqlparser.expression.Expression;
-import net.sf.jsqlparser.expression.LongValue;
-import org.apache.ibatis.mapping.MappedStatement;
-import org.apache.ibatis.reflection.MetaObject;
-import org.springframework.context.annotation.Bean;
-import org.springframework.stereotype.Service;
-
-import javax.servlet.http.HttpServletRequest;
-import java.util.ArrayList;
-import java.util.List;
-
-@Service
-public class TenantConfig {
-
-    @Bean
-    public PaginationInterceptor paginationInterceptor(HttpServletRequest request) {
-        PaginationInterceptor paginationInterceptor = new PaginationInterceptor();
-        List<ISqlParser> sqlParserList = new ArrayList<>();
-        TenantSqlParser tenantSqlParser = new TenantSqlParser();
-        tenantSqlParser.setTenantHandler(new TenantHandler() {
-            @Override
-            public Expression getTenantId() {
-                String token = request.getHeader("X-Access-Token");
-                Long tenantId = Tools.getTenantIdByToken(token);
-                if (tenantId!=0L) {
-                    return new LongValue(tenantId);
-                } else {
-                    //超管
-                    return null;
-                }
-            }
-
-            @Override
-            public String getTenantIdColumn() {
-                return "tenant_id";
-            }
-
-            @Override
-            public boolean doTableFilter(String tableName) {
-                //获取开启状态
-                Boolean res = true;
-                String token = request.getHeader("X-Access-Token");
-                Long tenantId = Tools.getTenantIdByToken(token);
-                if (tenantId!=0L) {
-                    // 这里可以判断是否过滤表
-                    if ("jsh_material_property".equals(tableName) || "jsh_sequence".equals(tableName)
-                            || "jsh_function".equals(tableName) || "jsh_platform_config".equals(tableName)
-                            || "jsh_tenant".equals(tableName)) {
-                        res = true;
-                    } else {
-                        res = false;
-                    }
-                }
-                return res;
-            }
-        });
-
-        sqlParserList.add(tenantSqlParser);
-        paginationInterceptor.setSqlParserList(sqlParserList);
-        paginationInterceptor.setSqlParserFilter(new ISqlParserFilter() {
-            @Override
-            public boolean doFilter(MetaObject metaObject) {
-                MappedStatement ms = SqlParserHelper.getMappedStatement(metaObject);
-                // 过滤自定义查询此时无租户信息约束出现
-                if ("com.jsh.erp.datasource.mappers.UserMapperEx.getUserByWeixinOpenId".equals(ms.getId())) {
-                    return true;
-                } else if ("com.jsh.erp.datasource.mappers.UserMapperEx.updateUserWithWeixinOpenId".equals(ms.getId())) {
-                    return true;
-                } else if ("com.jsh.erp.datasource.mappers.UserMapperEx.getUserListByUserNameOrLoginName".equals(ms.getId())) {
-                    return true;
-                } else if ("com.jsh.erp.datasource.mappers.UserMapperEx.disableUserByLimit".equals(ms.getId())) {
-                    return true;
-                } else if ("com.jsh.erp.datasource.mappers.RoleMapperEx.getRoleWithoutTenant".equals(ms.getId())) {
-                    return true;
-                } else if ("com.jsh.erp.datasource.mappers.LogMapperEx.insertLogWithUserId".equals(ms.getId())) {
-                    return true;
-                } else if ("com.jsh.erp.datasource.mappers.UserBusinessMapperEx.getBasicDataByKeyIdAndType".equals(ms.getId())) {
-                    return true;
-                }
-                return false;
-            }
-        });
-        return paginationInterceptor;
-    }
-
-    /**
-     * 相当于顶部的:
-     * {@code @MapperScan("com.jsh.erp.datasource.mappers*")}
-     * 这里可以扩展,比如使用配置文件来配置扫描Mapper的路径
-     */
-//    @Bean
-//    public MapperScannerConfigurer mapperScannerConfigurer() {
-//        MapperScannerConfigurer scannerConfigurer = new MapperScannerConfigurer();
-//        scannerConfigurer.setBasePackage("com.jsh.erp.datasource.mappers");
-//        return scannerConfigurer;
-//    }
-
-    /**
-     * 性能分析拦截器,不建议生产使用
-     */
-//    @Bean
-//    public PerformanceInterceptor performanceInterceptor(){
-//        return new PerformanceInterceptor();
-//    }
-
-
-}

+ 18 - 0
src/main/java/com/jsh/erp/controller/UserController.java

@@ -149,6 +149,24 @@ public class UserController extends BaseController {
         return res;
     }
 
+    @PostMapping(value = "/pdaLogin")
+    @ApiOperation(value = "PDA登录")
+    public BaseResponseInfo pdaLogin(@RequestBody UserEx userParam, HttpServletRequest request) throws Exception {
+        BaseResponseInfo res = new BaseResponseInfo();
+        try {
+            Map<String, Object> data = userService.login(userParam.getLoginName().trim(), userParam.getPassword().trim(), request);
+            res.code = 200;
+            res.data = data;
+        } catch (BusinessRunTimeException e) {
+            throw new BusinessRunTimeException(e.getCode(), e.getMessage());
+        } catch(Exception e){
+            logger.error(e.getMessage(), e);
+            res.code = 500;
+            res.data = "用户登录失败";
+        }
+        return res;
+    }
+
     @PostMapping(value = "/weixinLogin")
     @ApiOperation(value = "微信登录")
     public BaseResponseInfo weixinLogin(@RequestBody JSONObject jsonObject,

+ 143 - 0
src/main/java/com/jsh/erp/datasource/mappers/BaseMapperX.java

@@ -0,0 +1,143 @@
+package com.jsh.erp.datasource.mappers;
+
+import com.baomidou.mybatisplus.core.conditions.Wrapper;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.core.toolkit.support.SFunction;
+import com.github.pagehelper.PageHelper;
+import com.jsh.erp.query.LambdaQueryWrapperX;
+import com.jsh.erp.result.PageParam;
+import com.jsh.erp.result.PageResult;
+import com.jsh.erp.util.MyBatisUtils;
+import org.apache.ibatis.annotations.Param;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Objects;
+import java.util.function.Supplier;
+
+/**
+ * 在 MyBatis Plus 的 BaseMapper 的基础上拓展,提供更多的能力
+ */
+public interface BaseMapperX<T> extends BaseMapper<T> {
+
+    default PageResult<T> selectPage(PageParam pageParam, @Param("ew") Wrapper<T> queryWrapper) {
+        // MyBatis Plus 查询
+        IPage<T> mpPage = MyBatisUtils.buildPage(pageParam);
+        selectPage(mpPage, queryWrapper);
+        // 转换返回
+        return new PageResult<T>(mpPage.getRecords(), mpPage.getTotal(),(int)mpPage.getSize(),(int)mpPage.getCurrent());
+    }
+
+    default T selectOne(String field, Object value) {
+        return selectOne(new QueryWrapper<T>().eq(field, value));
+    }
+
+    /**
+     *  获取查询mapper
+     * @return
+     */
+    default LambdaQueryWrapperX<T> getWeekend() {
+        return new LambdaQueryWrapperX<>();
+    }
+
+    /**
+     *  自定义分页
+     * @param supplier
+     * @param pageQuery
+     * @param <T>
+     * @return
+     */
+    default <T> PageResult<T> pageSelect(Supplier<List<T>> supplier, PageParam pageQuery) {
+        if (Objects.equals(pageQuery.getPageNo(), 0)) {
+            List<T> list = supplier.get();
+            return PageResult.of(list, pageQuery);
+        } else {
+            com.github.pagehelper.Page<T> page = PageHelper.startPage(pageQuery.getPageNo(), pageQuery.getPageSize());
+            List<T> list = supplier.get();
+            PageResult<T> resultVO = PageResult.of(list, pageQuery);
+            resultVO.setTotal(page.getTotal());
+            resultVO.setPageNumber(page.getPageNum());
+            resultVO.setPageSize(page.getPageSize());
+            return resultVO;
+        }
+    }
+
+    default T selectOne(SFunction<T, ?> field, Object value) {
+        return selectOne(new LambdaQueryWrapper<T>().eq(field, value));
+    }
+
+    default T selectOne(String field1, Object value1, String field2, Object value2) {
+        return selectOne(new QueryWrapper<T>().eq(field1, value1).eq(field2, value2));
+    }
+
+    default T selectOne(SFunction<T, ?> field1, Object value1, SFunction<T, ?> field2, Object value2) {
+        return selectOne(new LambdaQueryWrapper<T>().eq(field1, value1).eq(field2, value2));
+    }
+
+    default Long selectCount() {
+        return selectCount(new QueryWrapper<T>());
+    }
+
+    default Long selectCount(String field, Object value) {
+        return selectCount(new QueryWrapper<T>().eq(field, value));
+    }
+
+    default Long selectCount(SFunction<T, ?> field, Object value) {
+        return selectCount(new LambdaQueryWrapper<T>().eq(field, value));
+    }
+
+    default Long selectCount(SFunction<T, ?> field1, Object value1, SFunction<T, ?> field2, Object value2) {
+        return selectCount(new LambdaQueryWrapper<T>().eq(field1, value1).eq(field2, value2));
+    }
+
+    default List<T> selectList() {
+        return selectList(new QueryWrapper<>());
+    }
+
+    default List<T> selectList(String field, Object value) {
+        return selectList(new QueryWrapper<T>().eq(field, value));
+    }
+
+    default List<T> selectList(SFunction<T, ?> field, Object value, SFunction<T, ?> field1, Object value1) {
+        return selectList(new LambdaQueryWrapper<T>().eq(field, value).eq(field1, value1));
+    }
+
+    default List<T> selectList(SFunction<T, ?> field, Object value) {
+        return selectList(new LambdaQueryWrapper<T>().eq(field, value));
+    }
+
+    default List<T> selectList(String field, Collection<?> values) {
+        return selectList(new QueryWrapper<T>().in(field, values));
+    }
+
+    default List<T> selectList(SFunction<T, ?> field, Collection<?> values) {
+        return selectList(new LambdaQueryWrapper<T>().in(field, values));
+    }
+    
+    default int deleteByProperty(SFunction<T, ?> field, Object value) {
+        if(value == null){
+            return -1;
+        }
+        return delete(new LambdaQueryWrapperX<T>().eq(field,value));
+    }
+
+    /**
+     * 逐条插入,适合少量数据插入,或者对性能要求不高的场景
+     *
+     * 如果大量,请使用 {@link com.baomidou.mybatisplus.extension.service.impl.ServiceImpl#saveBatch(Collection)} 方法
+     * 使用示例,可见 RoleMenuBatchInsertMapper、UserRoleBatchInsertMapper 类
+     *
+     * @param entities 实体们
+     */
+    default void insertBatch(Collection<T> entities) {
+        entities.forEach(this::insert);
+    }
+
+    default void updateBatch(T update) {
+        update(update, new QueryWrapper<>());
+    }
+
+}

+ 1 - 1
src/main/java/com/jsh/erp/datasource/mappers/LogMapper.java

@@ -5,7 +5,7 @@ import com.jsh.erp.datasource.entities.LogExample;
 import java.util.List;
 import org.apache.ibatis.annotations.Param;
 
-public interface LogMapper {
+public interface LogMapper extends BaseMapperX<Log> {
     long countByExample(LogExample example);
 
     int deleteByExample(LogExample example);

+ 128 - 0
src/main/java/com/jsh/erp/query/LambdaQueryWrapperX.java

@@ -0,0 +1,128 @@
+package com.jsh.erp.query;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.toolkit.ArrayUtils;
+import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
+import com.baomidou.mybatisplus.core.toolkit.support.SFunction;
+import org.springframework.util.StringUtils;
+
+import java.util.Collection;
+
+/**
+ * 拓展 MyBatis Plus QueryWrapper 类,主要增加如下功能:
+ *
+ * 1. 拼接条件的方法,增加 xxxIfPresent 方法,用于判断值不存在的时候,不要拼接到条件中。
+ *
+ * @param <T> 数据类型
+ */
+public class LambdaQueryWrapperX<T> extends LambdaQueryWrapper<T> {
+
+    public LambdaQueryWrapperX<T> likeIfPresent(SFunction<T, ?> column, String val) {
+        if (StringUtils.hasText(val)) {
+            return (LambdaQueryWrapperX<T>) super.like(column, val);
+        }
+        return this;
+    }
+
+    public LambdaQueryWrapperX<T> inIfPresent(SFunction<T, ?> column, Collection<?> values) {
+        if (!CollectionUtils.isEmpty(values)) {
+            return (LambdaQueryWrapperX<T>) super.in(column, values);
+        }
+        return this;
+    }
+
+    public LambdaQueryWrapperX<T> inIfPresent(SFunction<T, ?> column, Object... values) {
+        if (!ArrayUtils.isEmpty(values)) {
+            return (LambdaQueryWrapperX<T>) super.in(column, values);
+        }
+        return this;
+    }
+
+    public LambdaQueryWrapperX<T> eqIfPresent(SFunction<T, ?> column, Object val) {
+        if (val != null && !val.equals("")) {
+            return (LambdaQueryWrapperX<T>) super.eq(column, val);
+        }
+        return this;
+    }
+
+    public LambdaQueryWrapperX<T> neIfPresent(SFunction<T, ?> column, Object val) {
+        if (val != null) {
+            return (LambdaQueryWrapperX<T>) super.ne(column, val);
+        }
+        return this;
+    }
+
+    public LambdaQueryWrapperX<T> gtIfPresent(SFunction<T, ?> column, Object val) {
+        if (val != null) {
+            return (LambdaQueryWrapperX<T>) super.gt(column, val);
+        }
+        return this;
+    }
+
+    public LambdaQueryWrapperX<T> geIfPresent(SFunction<T, ?> column, Object val) {
+        if (val != null) {
+            return (LambdaQueryWrapperX<T>) super.ge(column, val);
+        }
+        return this;
+    }
+
+    public LambdaQueryWrapperX<T> ltIfPresent(SFunction<T, ?> column, Object val) {
+        if (val != null) {
+            return (LambdaQueryWrapperX<T>) super.lt(column, val);
+        }
+        return this;
+    }
+
+    public LambdaQueryWrapperX<T> leIfPresent(SFunction<T, ?> column, Object val) {
+        if (val != null) {
+            return (LambdaQueryWrapperX<T>) super.le(column, val);
+        }
+        return this;
+    }
+
+    public LambdaQueryWrapperX<T> betweenIfPresent(SFunction<T, ?> column, Object val1, Object val2) {
+        if (val1 != null && !val1.equals("") && val2 != null && !val2.equals("")) {
+            return (LambdaQueryWrapperX<T>) super.between(column, val1, val2);
+        }
+        if (val1 != null && !val1.equals("")) {
+            return (LambdaQueryWrapperX<T>) ge(column, val1);
+        }
+        if (val2 != null && !val2.equals("")) {
+            return (LambdaQueryWrapperX<T>) le(column, val2);
+        }
+        return this;
+    }
+
+    // ========== 重写父类方法,方便链式调用 ==========
+
+    @Override
+    public LambdaQueryWrapperX<T> eq(boolean condition, SFunction<T, ?> column, Object val) {
+        super.eq(condition, column, val);
+        return this;
+    }
+
+    @Override
+    public LambdaQueryWrapperX<T> eq(SFunction<T, ?> column, Object val) {
+        super.eq(column, val);
+        return this;
+    }
+
+    @Override
+    public LambdaQueryWrapperX<T> orderByDesc(SFunction<T, ?> column) {
+        super.orderByDesc(true, column);
+        return this;
+    }
+
+    @Override
+    public LambdaQueryWrapperX<T> last(String lastSql) {
+        super.last(lastSql);
+        return this;
+    }
+
+    @Override
+    public LambdaQueryWrapperX<T> in(SFunction<T, ?> column, Collection<?> coll) {
+        super.in(column, coll);
+        return this;
+    }
+
+}

+ 128 - 0
src/main/java/com/jsh/erp/query/QueryWrapperX.java

@@ -0,0 +1,128 @@
+package com.jsh.erp.query;
+
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.baomidou.mybatisplus.core.toolkit.ArrayUtils;
+import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
+import org.springframework.util.StringUtils;
+
+import java.util.Collection;
+
+/**
+ * 2022-09-29  龙涌
+ * 拓展 MyBatis Plus QueryWrapper 类,主要增加如下功能:
+ *
+ * 1. 拼接条件的方法,增加 xxxIfPresent 方法,用于判断值不存在的时候,不要拼接到条件中。
+ *
+ * @param <T> 数据类型
+ */
+public class QueryWrapperX<T> extends QueryWrapper<T> {
+
+    public QueryWrapperX<T> likeIfPresent(String column, String val) {
+        if (StringUtils.hasText(val)) {
+            return (QueryWrapperX<T>) super.like(column, val);
+        }
+        return this;
+    }
+
+    public QueryWrapperX<T> inIfPresent(String column, Collection<?> values) {
+        if (!CollectionUtils.isEmpty(values)) {
+            return (QueryWrapperX<T>) super.in(column, values);
+        }
+        return this;
+    }
+
+    public QueryWrapperX<T> inIfPresent(String column, Object... values) {
+        if (!ArrayUtils.isEmpty(values)) {
+            return (QueryWrapperX<T>) super.in(column, values);
+        }
+        return this;
+    }
+
+    public QueryWrapperX<T> eqIfPresent(String column, Object val) {
+        if (val != null) {
+            return (QueryWrapperX<T>) super.eq(column, val);
+        }
+        return this;
+    }
+
+    public QueryWrapperX<T> neIfPresent(String column, Object val) {
+        if (val != null) {
+            return (QueryWrapperX<T>) super.ne(column, val);
+        }
+        return this;
+    }
+
+    public QueryWrapperX<T> gtIfPresent(String column, Object val) {
+        if (val != null) {
+            return (QueryWrapperX<T>) super.gt(column, val);
+        }
+        return this;
+    }
+
+    public QueryWrapperX<T> geIfPresent(String column, Object val) {
+        if (val != null) {
+            return (QueryWrapperX<T>) super.ge(column, val);
+        }
+        return this;
+    }
+
+    public QueryWrapperX<T> ltIfPresent(String column, Object val) {
+        if (val != null) {
+            return (QueryWrapperX<T>) super.lt(column, val);
+        }
+        return this;
+    }
+
+    public QueryWrapperX<T> leIfPresent(String column, Object val) {
+        if (val != null) {
+            return (QueryWrapperX<T>) super.le(column, val);
+        }
+        return this;
+    }
+
+    public QueryWrapperX<T> betweenIfPresent(String column, Object val1, Object val2) {
+        if (val1 != null && val2 != null) {
+            return (QueryWrapperX<T>) super.between(column, val1, val2);
+        }
+        if (val1 != null) {
+            return (QueryWrapperX<T>) ge(column, val1);
+        }
+        if (val2 != null) {
+            return (QueryWrapperX<T>) le(column, val2);
+        }
+        return this;
+    }
+
+    // ========== 重写父类方法,方便链式调用 ==========
+
+    @Override
+    public QueryWrapperX<T> eq(boolean condition, String column, Object val) {
+        super.eq(condition, column, val);
+        return this;
+    }
+
+    @Override
+    public QueryWrapperX<T> eq(String column, Object val) {
+        super.eq(column, val);
+        return this;
+    }
+
+    @Override
+    public QueryWrapperX<T> orderByDesc(String column) {
+        super.orderByDesc(true, column);
+        return this;
+    }
+
+    @Override
+    public QueryWrapperX<T> last(String lastSql) {
+        super.last(lastSql);
+        return this;
+    }
+
+    @Override
+    public QueryWrapperX<T> in(String column, Collection<?> coll) {
+        super.in(column, coll);
+        return this;
+    }
+
+}

+ 37 - 0
src/main/java/com/jsh/erp/result/PageParam.java

@@ -0,0 +1,37 @@
+package com.jsh.erp.result;
+
+import cn.hutool.core.util.ObjectUtil;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import javax.validation.constraints.Max;
+import javax.validation.constraints.Min;
+import javax.validation.constraints.NotNull;
+import java.io.Serializable;
+
+@ApiModel("分页参数")
+@Data
+public class PageParam implements Serializable {
+
+    private static final Integer PAGE_NO = 1;
+    private static final Integer PAGE_SIZE = 10;
+
+    @ApiModelProperty(value = "页码,从 1 开始", required = true,example = "1")
+    @NotNull(message = "页码不能为空")
+    @Min(value = 1, message = "页码最小值为 1")
+    private Integer pageNo = PAGE_NO;
+    
+    private Integer pageNumber;
+
+    public Integer getPageNo() {
+        return ObjectUtil.defaultIfNull(pageNumber,pageNo);
+    }
+
+    @ApiModelProperty(value = "每页条数,最大值为 100", required = true, example = "10")
+    @NotNull(message = "每页条数不能为空")
+    @Min(value = 1, message = "每页条数最小值为 1")
+    @Max(value = 100, message = "每页条数最大值为 100")
+    private Integer pageSize = PAGE_SIZE;
+
+}

+ 66 - 0
src/main/java/com/jsh/erp/result/PageResult.java

@@ -0,0 +1,66 @@
+package com.jsh.erp.result;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.List;
+
+@ApiModel("分页结果")
+@Data
+public final class PageResult<T> implements Serializable {
+
+    @ApiModelProperty(value = "数据", required = true)
+    private List<T> list;
+    
+    @ApiModelProperty(value = "总量", required = true)
+    private Long total;
+    
+    private Integer pageSize;
+    
+    private Integer pageNumber;
+
+    public PageResult() {
+    }
+
+    public PageResult(List<T> list, Long total) {
+        this.list = list;
+        this.total = total;
+    }
+    public PageResult(List<T> list, Long total, Integer pageSize, Integer pageNumber) {
+        this.list = list;
+        this.total = total;
+        this.pageSize = pageSize;
+        this.pageNumber = pageNumber;
+    }
+    
+    public PageResult(List<T> list, PageResult oldResult) {
+        this.list = list;
+        this.total = oldResult.getTotal();
+        this.pageSize = oldResult.getPageSize();
+        this.pageNumber = oldResult.getPageNumber();
+    }
+
+    public PageResult(Long total) {
+        this.list = new ArrayList<>();
+        this.total = total;
+    }
+
+    public static <T> PageResult<T> empty() {
+        return new PageResult<>(0L);
+    }
+
+    public static <T> PageResult<T> empty(Long total) {
+        return new PageResult<>(total);
+    }
+
+    public static <T> PageResult<T> of(List<T> list, PageParam page) {
+        return new PageResult<>(list, (long) list.size(), page.getPageSize(), page.getPageNo());
+    }
+    
+    public static <T> PageResult<T> of(List<T> list, PageResult oldResult) {
+        return new PageResult<>(list, oldResult);
+    }
+}

+ 56 - 0
src/main/java/com/jsh/erp/result/SortingField.java

@@ -0,0 +1,56 @@
+package com.jsh.erp.result;
+
+import java.io.Serializable;
+
+/**
+ * 排序字段 DTO
+ *
+ * 类名加了 ing 的原因是,避免和 ES SortField 重名。
+ */
+public class SortingField implements Serializable {
+
+    /**
+     * 顺序 - 升序
+     */
+    public static final String ORDER_ASC = "asc";
+    /**
+     * 顺序 - 降序
+     */
+    public static final String ORDER_DESC = "desc";
+
+    /**
+     * 字段
+     */
+    private String field;
+    /**
+     * 顺序
+     */
+    private String order;
+
+    // 空构造方法,解决反序列化
+    public SortingField() {
+    }
+
+    public SortingField(String field, String order) {
+        this.field = field;
+        this.order = order;
+    }
+
+    public String getField() {
+        return field;
+    }
+
+    public SortingField setField(String field) {
+        this.field = field;
+        return this;
+    }
+
+    public String getOrder() {
+        return order;
+    }
+
+    public SortingField setOrder(String order) {
+        this.order = order;
+        return this;
+    }
+}

+ 11 - 152
src/main/java/com/jsh/erp/service/LogService.java

@@ -1,176 +1,35 @@
 package com.jsh.erp.service;
 
 import com.alibaba.fastjson.JSONObject;
-import com.jsh.erp.constants.BusinessConstants;
 import com.jsh.erp.datasource.entities.Log;
-import com.jsh.erp.datasource.entities.LogExample;
-import com.jsh.erp.datasource.mappers.LogMapper;
-import com.jsh.erp.datasource.mappers.LogMapperEx;
 import com.jsh.erp.datasource.vo.LogVo4List;
-import com.jsh.erp.exception.JshException;
-import com.jsh.erp.utils.PageUtils;
-import com.jsh.erp.utils.StringUtil;
-import com.jsh.erp.utils.Tools;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 
-import javax.annotation.Resource;
 import javax.servlet.http.HttpServletRequest;
-import java.util.Date;
 import java.util.List;
 
-import static com.jsh.erp.utils.Tools.getLocalIp;
+public interface LogService {
 
-@Service
-public class LogService {
-    private Logger logger = LoggerFactory.getLogger(LogService.class);
-    @Resource
-    private LogMapper logMapper;
+    Log getLog(long id)throws Exception;
 
-    @Resource
-    private LogMapperEx logMapperEx;
+    List<Log> getLog()throws Exception;
 
-    @Resource
-    private UserService userService;
-
-    @Resource
-    private RedisService redisService;
-
-    public Log getLog(long id)throws Exception {
-        Log result=null;
-        try{
-            result=logMapper.selectByPrimaryKey(id);
-        }catch(Exception e){
-            JshException.readFail(logger, e);
-        }
-        return result;
-    }
-
-    public List<Log> getLog()throws Exception {
-        LogExample example = new LogExample();
-        List<Log> list=null;
-        try{
-            list=logMapper.selectByExample(example);
-        }catch(Exception e){
-            JshException.readFail(logger, e);
-        }
-        return list;
-    }
-
-    public List<LogVo4List> select(String operation, String userInfo, String clientIp, String tenantLoginName, String tenantType,
-                                   String beginTime, String endTime, String content)throws Exception {
-        List<LogVo4List> list=null;
-        try{
-            beginTime = Tools.parseDayToTime(beginTime,BusinessConstants.DAY_FIRST_TIME);
-            endTime = Tools.parseDayToTime(endTime,BusinessConstants.DAY_LAST_TIME);
-            PageUtils.startPage();
-            list=logMapperEx.selectByConditionLog(operation, userInfo, clientIp, tenantLoginName, tenantType, beginTime, endTime,
-                    content);
-            if (null != list) {
-                for (LogVo4List log : list) {
-                    log.setCreateTimeStr(Tools.getCenternTime(log.getCreateTime()));
-                }
-            }
-        }catch(Exception e){
-            JshException.readFail(logger, e);
-        }
-        return list;
-    }
+    List<LogVo4List> select(String operation, String userInfo, String clientIp, String tenantLoginName, String tenantType,
+                            String beginTime, String endTime, String content)throws Exception;
 
     @Transactional(value = "transactionManager", rollbackFor = Exception.class)
-    public int insertLog(JSONObject obj, HttpServletRequest request) throws Exception{
-        Log log = JSONObject.parseObject(obj.toJSONString(), Log.class);
-        int result=0;
-        try{
-            result=logMapper.insertSelective(log);
-        }catch(Exception e){
-            JshException.writeFail(logger, e);
-        }
-        return result;
-    }
+    int insertLog(JSONObject obj, HttpServletRequest request) throws Exception;
 
     @Transactional(value = "transactionManager", rollbackFor = Exception.class)
-    public int updateLog(JSONObject obj, HttpServletRequest request)throws Exception {
-        Log log = JSONObject.parseObject(obj.toJSONString(), Log.class);
-        int result=0;
-        try{
-            result=logMapper.updateByPrimaryKeySelective(log);
-        }catch(Exception e){
-            JshException.writeFail(logger, e);
-        }
-        return result;
-    }
+    int updateLog(JSONObject obj, HttpServletRequest request)throws Exception;
 
     @Transactional(value = "transactionManager", rollbackFor = Exception.class)
-    public int deleteLog(Long id, HttpServletRequest request)throws Exception {
-        int result=0;
-        try{
-            result=logMapper.deleteByPrimaryKey(id);
-        }catch(Exception e){
-            JshException.writeFail(logger, e);
-        }
-        return result;
-    }
+    int deleteLog(Long id, HttpServletRequest request)throws Exception;
 
     @Transactional(value = "transactionManager", rollbackFor = Exception.class)
-    public int batchDeleteLog(String ids, HttpServletRequest request)throws Exception {
-        List<Long> idList = StringUtil.strToLongList(ids);
-        LogExample example = new LogExample();
-        example.createCriteria().andIdIn(idList);
-        int result=0;
-        try{
-            result=logMapper.deleteByExample(example);
-        }catch(Exception e){
-            JshException.writeFail(logger, e);
-        }
-        return result;
-    }
+    int batchDeleteLog(String ids, HttpServletRequest request)throws Exception;
 
-    public void insertLog(String moduleName, String content, HttpServletRequest request)throws Exception{
-        try{
-            Long userId = userService.getUserId(request);
-            if(userId!=null) {
-                String clientIp = getLocalIp(request);
-                String createTime = Tools.getNow3();
-                Long count = logMapperEx.getCountByIpAndDate(userId, moduleName, clientIp, createTime);
-                if(count > 0) {
-                    //如果某个用户某个IP在同1秒内连续操作两遍,此时需要删除该redis记录,使其退出,防止恶意攻击
-                    redisService.deleteObjectByUserAndIp(userId, clientIp);
-                } else {
-                    Log log = new Log();
-                    log.setUserId(userId);
-                    log.setOperation(moduleName);
-                    log.setClientIp(getLocalIp(request));
-                    log.setCreateTime(new Date());
-                    Byte status = 0;
-                    log.setStatus(status);
-                    log.setContent(content);
-                    logMapper.insertSelective(log);
-                }
-            }
-        }catch(Exception e){
-            JshException.writeFail(logger, e);
-        }
-    }
+    void insertLog(String moduleName, String content, HttpServletRequest request)throws Exception;
 
-    public void insertLogWithUserId(Long userId, Long tenantId, String moduleName, String content, HttpServletRequest request)throws Exception{
-        try{
-            if(userId!=null) {
-                Log log = new Log();
-                log.setUserId(userId);
-                log.setOperation(moduleName);
-                log.setClientIp(getLocalIp(request));
-                log.setCreateTime(new Date());
-                Byte status = 0;
-                log.setStatus(status);
-                log.setContent(content);
-                log.setTenantId(tenantId);
-                logMapperEx.insertLogWithUserId(log);
-            }
-        }catch(Exception e){
-            JshException.writeFail(logger, e);
-        }
-    }
+    void insertLogWithUserId(Long userId, Long tenantId, String moduleName, String content, HttpServletRequest request)throws Exception;
 }

+ 189 - 0
src/main/java/com/jsh/erp/service/impl/LogServiceImpl.java

@@ -0,0 +1,189 @@
+package com.jsh.erp.service.impl;
+
+import com.alibaba.fastjson.JSONObject;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.jsh.erp.constants.BusinessConstants;
+import com.jsh.erp.datasource.entities.Log;
+import com.jsh.erp.datasource.entities.LogExample;
+import com.jsh.erp.datasource.mappers.LogMapper;
+import com.jsh.erp.datasource.mappers.LogMapperEx;
+import com.jsh.erp.datasource.vo.LogVo4List;
+import com.jsh.erp.exception.JshException;
+import com.jsh.erp.service.LogService;
+import com.jsh.erp.service.RedisService;
+import com.jsh.erp.service.UserService;
+import com.jsh.erp.utils.PageUtils;
+import com.jsh.erp.utils.StringUtil;
+import com.jsh.erp.utils.Tools;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import javax.annotation.Resource;
+import javax.servlet.http.HttpServletRequest;
+import java.util.Date;
+import java.util.List;
+
+import static com.jsh.erp.utils.Tools.getLocalIp;
+
+@Service
+public class LogServiceImpl extends ServiceImpl<LogMapper, Log> implements LogService {
+    private Logger logger = LoggerFactory.getLogger(LogServiceImpl.class);
+    @Resource
+    private LogMapper logMapper;
+
+    @Resource
+    private LogMapperEx logMapperEx;
+
+    @Resource
+    private UserService userService;
+
+    @Resource
+    private RedisService redisService;
+
+    @Override
+    public Log getLog(long id)throws Exception {
+        Log result=null;
+        try{
+            result=logMapper.selectByPrimaryKey(id);
+        }catch(Exception e){
+            JshException.readFail(logger, e);
+        }
+        return result;
+    }
+
+    @Override
+    public List<Log> getLog()throws Exception {
+        LogExample example = new LogExample();
+        List<Log> list=null;
+        try{
+            list=logMapper.selectByExample(example);
+        }catch(Exception e){
+            JshException.readFail(logger, e);
+        }
+        return list;
+    }
+
+    @Override
+    public List<LogVo4List> select(String operation, String userInfo, String clientIp, String tenantLoginName, String tenantType,
+                                   String beginTime, String endTime, String content)throws Exception {
+        List<LogVo4List> list=null;
+        try{
+            beginTime = Tools.parseDayToTime(beginTime,BusinessConstants.DAY_FIRST_TIME);
+            endTime = Tools.parseDayToTime(endTime,BusinessConstants.DAY_LAST_TIME);
+            PageUtils.startPage();
+            list=logMapperEx.selectByConditionLog(operation, userInfo, clientIp, tenantLoginName, tenantType, beginTime, endTime,
+                    content);
+            if (null != list) {
+                for (LogVo4List log : list) {
+                    log.setCreateTimeStr(Tools.getCenternTime(log.getCreateTime()));
+                }
+            }
+        }catch(Exception e){
+            JshException.readFail(logger, e);
+        }
+        return list;
+    }
+
+    @Transactional(value = "transactionManager", rollbackFor = Exception.class)
+    @Override
+    public int insertLog(JSONObject obj, HttpServletRequest request) throws Exception{
+        Log log = JSONObject.parseObject(obj.toJSONString(), Log.class);
+        int result=0;
+        try{
+            result=logMapper.insertSelective(log);
+        }catch(Exception e){
+            JshException.writeFail(logger, e);
+        }
+        return result;
+    }
+
+    @Override
+    @Transactional(value = "transactionManager", rollbackFor = Exception.class)
+    public int updateLog(JSONObject obj, HttpServletRequest request)throws Exception {
+        Log log = JSONObject.parseObject(obj.toJSONString(), Log.class);
+        int result=0;
+        try{
+            result=logMapper.updateByPrimaryKeySelective(log);
+        }catch(Exception e){
+            JshException.writeFail(logger, e);
+        }
+        return result;
+    }
+
+    @Override
+    @Transactional(value = "transactionManager", rollbackFor = Exception.class)
+    public int deleteLog(Long id, HttpServletRequest request)throws Exception {
+        int result=0;
+        try{
+            result=logMapper.deleteByPrimaryKey(id);
+        }catch(Exception e){
+            JshException.writeFail(logger, e);
+        }
+        return result;
+    }
+
+    @Override
+    @Transactional(value = "transactionManager", rollbackFor = Exception.class)
+    public int batchDeleteLog(String ids, HttpServletRequest request)throws Exception {
+        List<Long> idList = StringUtil.strToLongList(ids);
+        LogExample example = new LogExample();
+        example.createCriteria().andIdIn(idList);
+        int result=0;
+        try{
+            result=logMapper.deleteByExample(example);
+        }catch(Exception e){
+            JshException.writeFail(logger, e);
+        }
+        return result;
+    }
+
+    @Override
+    public void insertLog(String moduleName, String content, HttpServletRequest request)throws Exception{
+        try{
+            Long userId = userService.getUserId(request);
+            if(userId!=null) {
+                String clientIp = getLocalIp(request);
+                String createTime = Tools.getNow3();
+                Long count = logMapperEx.getCountByIpAndDate(userId, moduleName, clientIp, createTime);
+                if(count > 0) {
+                    //如果某个用户某个IP在同1秒内连续操作两遍,此时需要删除该redis记录,使其退出,防止恶意攻击
+                    redisService.deleteObjectByUserAndIp(userId, clientIp);
+                } else {
+                    Log log = new Log();
+                    log.setUserId(userId);
+                    log.setOperation(moduleName);
+                    log.setClientIp(getLocalIp(request));
+                    log.setCreateTime(new Date());
+                    Byte status = 0;
+                    log.setStatus(status);
+                    log.setContent(content);
+                    logMapper.insertSelective(log);
+                }
+            }
+        }catch(Exception e){
+            JshException.writeFail(logger, e);
+        }
+    }
+
+    @Override
+    public void insertLogWithUserId(Long userId, Long tenantId, String moduleName, String content, HttpServletRequest request)throws Exception{
+        try{
+            if(userId!=null) {
+                Log log = new Log();
+                log.setUserId(userId);
+                log.setOperation(moduleName);
+                log.setClientIp(getLocalIp(request));
+                log.setCreateTime(new Date());
+                Byte status = 0;
+                log.setStatus(status);
+                log.setContent(content);
+                log.setTenantId(tenantId);
+                logMapperEx.insertLogWithUserId(log);
+            }
+        }catch(Exception e){
+            JshException.writeFail(logger, e);
+        }
+    }
+}

+ 13 - 0
src/main/java/com/jsh/erp/util/BigDecimalUtils.java

@@ -0,0 +1,13 @@
+package com.jsh.erp.util;
+
+import java.math.BigDecimal;
+
+public class BigDecimalUtils {
+
+    public static BigDecimal customDivide(BigDecimal dividend, BigDecimal divisor) {
+        if (dividend.compareTo(BigDecimal.ZERO) == 0 || divisor.compareTo(BigDecimal.ZERO) == 0) {
+            return BigDecimal.ZERO;
+        }
+        return dividend.divide(divisor);
+    }
+}

+ 44 - 0
src/main/java/com/jsh/erp/util/DistanceCalculatorUtils.java

@@ -0,0 +1,44 @@
+package com.jsh.erp.util;
+
+public class DistanceCalculatorUtils {
+    // 地球的平均半径,单位:米
+    private static final double EARTH_RADIUS = 6371000;
+
+    /**
+     * 将角度转换为弧度
+     * @param degrees 角度值
+     * @return 弧度值
+     */
+    private static double degreesToRadians(double degrees) {
+        return degrees * Math.PI / 180;
+    }
+
+    /**
+     * 根据经纬度计算两点之间的距离,单位为米
+     * @param currentLat 当前点的纬度
+     * @param currentLon 当前点的经度
+     * @param targetLat 目标点的纬度
+     * @param targetLon 目标点的经度
+     * @return 两点之间的距离,单位:米
+     */
+    public static double calculateDistance(double currentLat, double currentLon, double targetLat, double targetLon) {
+        // 将经纬度从度转换为弧度
+        double lat1 = degreesToRadians(currentLat);
+        double lon1 = degreesToRadians(currentLon);
+        double lat2 = degreesToRadians(targetLat);
+        double lon2 = degreesToRadians(targetLon);
+
+        // 计算纬度差和经度差
+        double dLat = lat2 - lat1;
+        double dLon = lon2 - lon1;
+
+        // Haversine 公式
+        double a = Math.sin(dLat / 2) * Math.sin(dLat / 2) +
+                Math.cos(lat1) * Math.cos(lat2) *
+                        Math.sin(dLon / 2) * Math.sin(dLon / 2);
+        double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
+
+        // 计算距离
+        return EARTH_RADIUS * c;
+    }
+}

+ 44 - 0
src/main/java/com/jsh/erp/util/EmailTask.java

@@ -0,0 +1,44 @@
+package com.jsh.erp.util;
+
+import org.springframework.boot.autoconfigure.mail.MailProperties;
+import org.springframework.mail.javamail.JavaMailSender;
+import org.springframework.mail.javamail.MimeMessageHelper;
+
+import javax.mail.MessagingException;
+import javax.mail.internet.MimeMessage;
+
+public class EmailTask implements Runnable {
+
+    private final JavaMailSender mailSender;
+    private final MailProperties mailProperties;
+    private final String to;
+    private final String subject;
+    private final String text;
+
+    public EmailTask(JavaMailSender mailSender,MailProperties mailProperties, String to, String subject, String text) {
+        this.mailProperties = mailProperties;
+        this.mailSender = mailSender;
+        this.to = to;
+        this.subject = subject;
+        this.text = text;
+    }
+
+    @Override
+    public void run() {
+        try {
+            System.setProperty("mail.mime.splitlongparameters", "false");
+            // 创建邮件消息
+            MimeMessage message = mailSender.createMimeMessage();
+            MimeMessageHelper helper = new MimeMessageHelper(message, true);
+            helper.setFrom(mailProperties.getUsername());
+            helper.setTo(to);
+            helper.setSubject(subject);
+            helper.setText(text);
+            // 发送邮件
+            mailSender.send(message);
+            System.out.println("邮件发送成功:" + to);
+        } catch (MessagingException e) {
+            System.err.println("邮件发送失败:" + to + ",错误信息:" + e.getMessage());
+        }
+    }
+}

+ 88 - 0
src/main/java/com/jsh/erp/util/MyBatisUtils.java

@@ -0,0 +1,88 @@
+package com.jsh.erp.util;
+
+import cn.hutool.core.collection.CollectionUtil;
+import com.baomidou.mybatisplus.core.metadata.OrderItem;
+import com.baomidou.mybatisplus.core.toolkit.StringPool;
+import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
+import com.baomidou.mybatisplus.extension.plugins.inner.InnerInterceptor;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.jsh.erp.result.PageParam;
+import com.jsh.erp.result.SortingField;
+import net.sf.jsqlparser.expression.Alias;
+import net.sf.jsqlparser.schema.Column;
+import net.sf.jsqlparser.schema.Table;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * MyBatis 工具类
+ */
+public class MyBatisUtils {
+
+    private static final String MYSQL_ESCAPE_CHARACTER = "`";
+
+    public static <T> Page<T> buildPage(PageParam pageParam) {
+        return buildPage(pageParam, null);
+    }
+
+    public static <T> Page<T> buildPage(PageParam pageParam, Collection<SortingField> sortingFields) {
+        // 页码 + 数量
+        Page<T> page = new Page<>(pageParam.getPageNo(), pageParam.getPageSize());
+        // 排序字段
+        if (!CollectionUtil.isEmpty(sortingFields)) {
+            page.addOrder(sortingFields.stream().map(sortingField -> SortingField.ORDER_ASC.equals(sortingField.getOrder()) ?
+                    OrderItem.asc(sortingField.getField()) : OrderItem.desc(sortingField.getField()))
+                    .collect(Collectors.toList()));
+        }
+        return page;
+    }
+
+    /**
+     * 将拦截器添加到链中
+     * 由于 MybatisPlusInterceptor 不支持添加拦截器,所以只能全量设置
+     *
+     * @param interceptor 链
+     * @param inner 拦截器
+     * @param index 位置
+     */
+    public static void addInterceptor(MybatisPlusInterceptor interceptor, InnerInterceptor inner, int index) {
+        List<InnerInterceptor> inners = new ArrayList<>(interceptor.getInterceptors());
+        inners.add(index, inner);
+        interceptor.setInterceptors(inners);
+    }
+
+    /**
+     * 获得 Table 对应的表名
+     *
+     * 兼容 MySQL 转义表名 `t_xxx`
+     *
+     * @param table 表
+     * @return 去除转移字符后的表名
+     */
+    public static String getTableName(Table table) {
+        String tableName = table.getName();
+        if (tableName.startsWith(MYSQL_ESCAPE_CHARACTER) && tableName.endsWith(MYSQL_ESCAPE_CHARACTER)) {
+            tableName = tableName.substring(1, tableName.length() - 1);
+        }
+        return tableName;
+    }
+
+    /**
+     * 构建 Column 对象
+     *
+     * @param tableName 表名
+     * @param tableAlias 别名
+     * @param column 字段名
+     * @return Column 对象
+     */
+    public static Column buildColumn(String tableName, Alias tableAlias, String column) {
+        if (tableAlias != null) {
+            tableName = tableAlias.getName();
+        }
+        return new Column(tableName + StringPool.DOT + column);
+    }
+
+}