From dc63a516387067e207ac09dd7fd23103afadab97 Mon Sep 17 00:00:00 2001 From: zhouhefeng Date: Fri, 11 Sep 2026 15:56:51 +0800 Subject: [PATCH] commit --- .../satoken/aop/SaCheckLoginInterceptor.java | 4 + .../aop/SaCheckPermissionInterceptor.java | 4 + .../satoken/aop/SaCheckRoleInterceptor.java | 4 + ...ssWalkActivityBindingRecordController.java | 111 +- .../FitnessWalkActivityManageController.java | 363 +--- .../FitnessWalkLoginController.java | 100 +- .../FitnessWalkPunchLotteryController.java | 227 +-- .../FitnessWalkPunchStatisticsController.java | 219 +-- .../FitnessWalkStepManageController.java | 375 +--- .../FitnessWalkStepRaffleController.java | 272 +-- .../FitnessWalkStepRankingController.java | 349 ++-- .../FitnessWalkStepStatisticsController.java | 139 +- .../FitnessWalkStepWiningController.java | 108 +- .../model/FitnessWalkActivity.java | 33 +- .../service/FitnessWalkCommonService.java | 154 +- .../FitnessWalkPunchStatisticsService.java | 178 +- .../service/FitnessWalkStepRaffleService.java | 96 +- .../impl/FitnessWalkCommonServiceImpl.java | 962 ++++++++++- ...FitnessWalkPunchStatisticsServiceImpl.java | 1199 ++++++++++++- .../FitnessWalkStepRaffleServiceImpl.java | 317 +++- .../fitnesswalk/utils/WeAppCloudUtil.java | 71 +- .../platform/js/mixin/initTableMixins.js | 3 +- .../activity/fitnesswalk/activityManage.html | 1526 +++++++++++------ .../activity/fitnesswalk/bindingRecord.html | 242 ++- .../activity/fitnesswalk/punchStatistics.html | 353 ++-- .../zhgh/activity/fitnesswalk/stepRaffle.html | 376 ++-- .../activity/fitnesswalk/stepRanking.html | 269 ++- .../activity/fitnesswalk/stepStatistics.html | 232 +-- .../zhgh/activity/fitnesswalk/stepWining.html | 209 ++- .../familyPlanningOffice/index.html | 3 +- .../familyPlanningOffice/index.html | 3 +- 31 files changed, 5433 insertions(+), 3068 deletions(-) diff --git a/src/main/java/com/budwk/app/web/commons/auth/satoken/aop/SaCheckLoginInterceptor.java b/src/main/java/com/budwk/app/web/commons/auth/satoken/aop/SaCheckLoginInterceptor.java index 04fd258..6f9644f 100644 --- a/src/main/java/com/budwk/app/web/commons/auth/satoken/aop/SaCheckLoginInterceptor.java +++ b/src/main/java/com/budwk/app/web/commons/auth/satoken/aop/SaCheckLoginInterceptor.java @@ -15,6 +15,10 @@ import java.lang.reflect.Method; public class SaCheckLoginInterceptor implements MethodInterceptor { @Override public void filter(InterceptorChain chain) throws Throwable { + if (SaTokenAuthIgnoreUtil.isIgnore()) { + chain.doChain(); + return; + } Method method = chain.getCallingMethod(); SaCheckLogin at = method.getAnnotation(SaCheckLogin.class); SaManager.getStpLogic(at.type()).checkByAnnotation(at); diff --git a/src/main/java/com/budwk/app/web/commons/auth/satoken/aop/SaCheckPermissionInterceptor.java b/src/main/java/com/budwk/app/web/commons/auth/satoken/aop/SaCheckPermissionInterceptor.java index c1004bf..2eac099 100644 --- a/src/main/java/com/budwk/app/web/commons/auth/satoken/aop/SaCheckPermissionInterceptor.java +++ b/src/main/java/com/budwk/app/web/commons/auth/satoken/aop/SaCheckPermissionInterceptor.java @@ -15,6 +15,10 @@ import java.lang.reflect.Method; public class SaCheckPermissionInterceptor implements MethodInterceptor { @Override public void filter(InterceptorChain chain) throws Throwable { + if (SaTokenAuthIgnoreUtil.isIgnore()) { + chain.doChain(); + return; + } Method method = chain.getCallingMethod(); SaCheckPermission at = method.getAnnotation(SaCheckPermission.class); SaManager.getStpLogic(at.type()).checkByAnnotation(at); diff --git a/src/main/java/com/budwk/app/web/commons/auth/satoken/aop/SaCheckRoleInterceptor.java b/src/main/java/com/budwk/app/web/commons/auth/satoken/aop/SaCheckRoleInterceptor.java index 7df25c0..c87fd80 100644 --- a/src/main/java/com/budwk/app/web/commons/auth/satoken/aop/SaCheckRoleInterceptor.java +++ b/src/main/java/com/budwk/app/web/commons/auth/satoken/aop/SaCheckRoleInterceptor.java @@ -15,6 +15,10 @@ import java.lang.reflect.Method; public class SaCheckRoleInterceptor implements MethodInterceptor { @Override public void filter(InterceptorChain chain) throws Throwable { + if (SaTokenAuthIgnoreUtil.isIgnore()) { + chain.doChain(); + return; + } Method method = chain.getCallingMethod(); SaCheckRole at = method.getAnnotation(SaCheckRole.class); SaManager.getStpLogic(at.type()).checkByAnnotation(at); diff --git a/src/main/java/com/budwk/app/zhgh/activity/fitnesswalk/controller/FitnessWalkActivityBindingRecordController.java b/src/main/java/com/budwk/app/zhgh/activity/fitnesswalk/controller/FitnessWalkActivityBindingRecordController.java index 8bb51c6..1c6cfac 100644 --- a/src/main/java/com/budwk/app/zhgh/activity/fitnesswalk/controller/FitnessWalkActivityBindingRecordController.java +++ b/src/main/java/com/budwk/app/zhgh/activity/fitnesswalk/controller/FitnessWalkActivityBindingRecordController.java @@ -1,38 +1,26 @@ package com.budwk.app.zhgh.activity.fitnesswalk.controller; import cn.dev33.satoken.annotation.SaCheckPermission; -import cn.hutool.core.util.StrUtil; -import cn.hutool.json.JSONObject; -import cn.hutool.json.JSONUtil; import com.budwk.app.base.annotation.SLog; -import com.budwk.app.base.constant.RoleConstant; -import com.budwk.app.base.page.Pagination; import com.budwk.app.base.result.Result; -import com.budwk.app.web.commons.auth.utils.AuthUtil; -import com.budwk.app.web.commons.auth.utils.SecurityUtil; -import com.budwk.app.zhgh.activity.fitnesswalk.utils.WeAppCloudUtil; +import com.budwk.app.zhgh.activity.fitnesswalk.service.FitnessWalkBindingRecordService; import io.swagger.annotations.ApiOperation; +import org.nutz.aop.interceptor.ioc.TransAop; +import org.nutz.ioc.aop.Aop; import org.nutz.ioc.loader.annotation.Inject; import org.nutz.ioc.loader.annotation.IocBean; -import org.nutz.json.Json; -import org.nutz.lang.Strings; -import org.nutz.lang.util.NutMap; import org.nutz.mvc.annotation.At; import org.nutz.mvc.annotation.Ok; import org.nutz.mvc.annotation.Param; - import java.io.IOException; -import java.util.List; -import java.util.stream.Collectors; @IocBean @Ok("json") -@ApiOperation("健步走-同步绑定记录") @At("/platform/fitnessWalk/bindingRecord") public class FitnessWalkActivityBindingRecordController { @Inject - private WeAppCloudUtil weAppCloudUtil; + private FitnessWalkBindingRecordService fitnessWalkBindingRecordService; @At("") @Ok("beetl:/platform/zhgh/activity/fitnesswalk/bindingRecord.html") @@ -41,93 +29,58 @@ public class FitnessWalkActivityBindingRecordController { } /** - * 获取用户绑定记录 - * @param keyWord - * @param unionId - * @param searchName - * @param searchKeyword - * @param pageNumber - * @param pageSize - * @param pageOrderName - * @param pageOrderBy - * @return - * @throws IOException + * 按工号、姓名及分工会权限分页检索绑定记录。 + * + * @param keyWord 绑定记录的姓名或工号关键字 + * @param unionId 分工会ID,空值表示不追加筛选,已有数据权限仍生效 + * @param searchName 查询字段,使用页面提供的字段名 + * @param searchKeyword 姓名、工号或当前查询字段的检索内容 + * @param pageNumber 页码,从1开始 + * @param pageSize 每页记录数 + * @param pageOrderName 列表排序字段 + * @param pageOrderBy 排序方向,ascending或descending + * @return Result,code为0表示成功,data承载按工号、姓名及分工会权限分页检索绑定记录结果,失败说明见msg */ @At - @ApiOperation("获取用户绑定记录") @SaCheckPermission("fitnessWalk.bindingRecord") + @ApiOperation("获取用户绑定记录") public Result pageData(@Param("keyWord") String keyWord, @Param("unionId") String unionId, @Param("searchName") String searchName, @Param("searchKeyword") String searchKeyword, - @Param("pageNumber") int pageNumber, @Param("pageSize") int pageSize, + @Param("pageNumber") int pageNumber,@Param("pageSize") int pageSize, @Param("pageOrderName") String pageOrderName, - @Param("pageOrderBy") String pageOrderBy) { - int skip = (pageNumber == 1 ? 0 : (pageNumber - 1)) * pageSize; - - StringBuilder sql = new StringBuilder("db.collection('login_record')"); - if (StrUtil.isNotBlank(keyWord)) { - sql.append(".where("); - sql.append("_.or(["); - sql.append("{loginname:{$regex:'").append(keyWord).append("',$options:'i'}},"); - sql.append("{username:{$regex:'").append(keyWord).append("',$options:'i'}}"); - sql.append("])"); - sql.append(")"); - } - if (!AuthUtil.hasRoleOr(RoleConstant.SYSADMIN.name(), RoleConstant.SCHOOL_UNION_ADMIN.name())) { - sql.append(".where({'data.unionid':'").append(SecurityUtil.getUnionId()).append("'})"); - } else { - if (Strings.isNotBlank(unionId)) { - sql.append(".where({'data.unionid':'").append(unionId).append("'})"); - } - } - sql.append(".field({ data: false })"); - sql.append(".skip(").append(skip).append(")"); - sql.append(".limit(").append(pageSize).append(")"); - sql.append(".get()"); - - try { - JSONObject jsonObject = weAppCloudUtil.request(WeAppCloudUtil.CRUD.QUERY, sql.toString()); - List data = jsonObject.getJSONArray("data").stream() - .map(v -> JSONUtil.toBean((String) v, NutMap.class)) - .collect(Collectors.toList()); - JSONObject pager = jsonObject.getJSONObject("pager"); - Pagination pagination = new Pagination(pageNumber, pageSize, pager.getInt("Total"), data); - return Result.success().addData(pagination); - } catch (Exception e) { - return Result.error(e.getMessage()); - } + @Param("pageOrderBy") String pageOrderBy) throws IOException { + return fitnessWalkBindingRecordService.pageData(keyWord, unionId, searchName, searchKeyword, pageNumber, pageSize, pageOrderName, pageOrderBy); } - /** - * 删除绑定记录 - * @param ids - * @return + * 按记录ID删除选中的云端绑定记录。 + * + * @param ids 待删除记录的主键ID数组 + * @return Result,code为0表示成功,data承载按记录ID删除选中的云端绑定记录结果,失败说明见msg */ @At + @SaCheckPermission("fitnessWalk.bindingRecord") + @Aop(TransAop.READ_COMMITTED) @ApiOperation("删除绑定记录") @SLog(tag = "健步走-同步绑定记录", msg = "删除绑定记录") - @SaCheckPermission("fitnessWalk.bindingRecord") public Result delete(String[] ids) { - String sql = "db.collection('login_record').where({_id:_.in(" + Json.toJson(ids) + ")}).remove()"; - weAppCloudUtil.request(WeAppCloudUtil.CRUD.DELETE, sql); - return Result.success(); + return fitnessWalkBindingRecordService.delete(ids); } /** - * 删除所有绑定记录 - * @return + * 清除云端绑定记录,供管理员重新同步人员绑定。 + * + * @return Result,code为0表示成功,data承载清除云端绑定记录,供管理员重新同步人员绑定结果,失败说明见msg */ @At + @SaCheckPermission("fitnessWalk.bindingRecord") + @Aop(TransAop.READ_COMMITTED) @ApiOperation("删除所有绑定记录") @SLog(tag = "健步走-同步绑定记录", msg = "删除所有绑定记录") - @SaCheckPermission("fitnessWalk.bindingRecord") public Result deleteAll() { - String sql = "db.collection('login_record').where({_id: _.neq('0')}).remove()"; - weAppCloudUtil.request(WeAppCloudUtil.CRUD.DELETE, sql); - return Result.success(); + return fitnessWalkBindingRecordService.deleteAll(); } - } diff --git a/src/main/java/com/budwk/app/zhgh/activity/fitnesswalk/controller/FitnessWalkActivityManageController.java b/src/main/java/com/budwk/app/zhgh/activity/fitnesswalk/controller/FitnessWalkActivityManageController.java index 81a3cd2..37db6cb 100644 --- a/src/main/java/com/budwk/app/zhgh/activity/fitnesswalk/controller/FitnessWalkActivityManageController.java +++ b/src/main/java/com/budwk/app/zhgh/activity/fitnesswalk/controller/FitnessWalkActivityManageController.java @@ -1,337 +1,160 @@ package com.budwk.app.zhgh.activity.fitnesswalk.controller; import cn.dev33.satoken.annotation.SaCheckPermission; -import cn.hutool.core.date.DateTime; -import cn.hutool.core.date.DateUtil; -import cn.hutool.core.util.StrUtil; -import cn.hutool.json.JSONObject; -import cn.hutool.json.JSONUtil; import com.budwk.app.base.annotation.SLog; -import com.budwk.app.base.constant.RoleConstant; -import com.budwk.app.base.page.Pagination; import com.budwk.app.base.result.Result; -import com.budwk.app.sys.models.Sys_task; -import com.budwk.app.sys.services.SysTaskService; -import com.budwk.app.sys.services.SysUserService; -import com.budwk.app.task.services.TaskPlatformService; -import com.budwk.app.web.commons.auth.utils.AuthUtil; -import com.budwk.app.web.commons.auth.utils.SecurityUtil; import com.budwk.app.zhgh.activity.fitnesswalk.model.FitnessWalkActivity; +import com.budwk.app.zhgh.activity.fitnesswalk.service.FitnessWalkActivityService; import com.budwk.app.zhgh.activity.fitnesswalk.service.FitnessWalkCommonService; -import com.budwk.app.zhgh.activity.fitnesswalk.utils.WeAppCloudUtil; import io.swagger.annotations.ApiOperation; -import org.nutz.dao.Cnd; +import org.nutz.aop.interceptor.ioc.TransAop; +import org.nutz.ioc.aop.Aop; import org.nutz.ioc.loader.annotation.Inject; import org.nutz.ioc.loader.annotation.IocBean; -import org.nutz.json.Json; -import org.nutz.lang.Lang; -import org.nutz.lang.Strings; -import org.nutz.lang.util.NutMap; import org.nutz.mvc.annotation.AdaptBy; import org.nutz.mvc.annotation.At; import org.nutz.mvc.annotation.Ok; import org.nutz.mvc.annotation.Param; import org.nutz.mvc.upload.TempFile; import org.nutz.mvc.upload.UploadAdaptor; - -import java.text.SimpleDateFormat; import java.util.*; -import java.util.stream.Collectors; - -@IocBean -@Ok("json") -@ApiOperation("健步走-活动管理") @At("/platform/fitnessWalk/activityManage") +@Ok("json") +@IocBean public class FitnessWalkActivityManageController { - @Inject - private WeAppCloudUtil weAppCloudUtil; - @Inject - private SysTaskService sysTaskService; - @Inject - private SysUserService sysUserService; - @Inject - private TaskPlatformService taskPlatformService; + @Inject private FitnessWalkCommonService fitnessWalkCommonService; + @Inject + private FitnessWalkActivityService fitnessWalkActivityService; + @At("") @Ok("beetl:/platform/zhgh/activity/fitnesswalk/activityManage.html") @SaCheckPermission("fitnessWalk.activityManage") public void index() { } + /** + * 分页检索云端活动并保留当前分工会数据权限。 + * + * @param year 查询年度,四位年份 + * @param unionId 分工会ID,空值表示不追加筛选,已有数据权限仍生效 + * @param searchName 查询字段,使用页面提供的字段名 + * @param searchKeyword 姓名、工号或当前查询字段的检索内容 + * @param pageNumber 页码,从1开始 + * @param pageSize 每页记录数 + * @param pageOrderName 列表排序字段 + * @param pageOrderBy 排序方向,ascending或descending + * @return Result,code为0表示成功,data承载分页检索云端活动并保留当前分工会数据权限结果,失败说明见msg + */ @At - @ApiOperation("获取活动数据") @SaCheckPermission("fitnessWalk.activityManage") - public Result pageData(@Param("year") Integer year, @Param("unionId") String unionId, @Param("searchName") String searchName, - @Param("searchKeyword") String searchKeyword, @Param("pageNumber") int pageNumber, - @Param("pageSize") int pageSize, @Param("pageOrderName") String pageOrderName, - @Param("pageOrderBy") String pageOrderBy) { - - int skip = (pageNumber == 1 ? 0 : (pageNumber - 1)) * pageSize; - - StringBuilder sql = new StringBuilder("db.collection('activity')"); - if (Strings.isNotBlank(searchName) && Strings.isNotBlank(searchKeyword)) { - sql.append("where("); - sql.append("_.or(["); - sql.append("{name:{$regex:'").append(searchKeyword).append("',$options:'i'}}"); - sql.append("{tag:{$regex:'").append(searchKeyword).append("',$options:'i'}}"); - sql.append("])"); - sql.append(")"); - } - if (!AuthUtil.hasRoleOr(RoleConstant.SYSADMIN.name(), RoleConstant.SCHOOL_UNION_ADMIN.name())) { - sql.append(".where({unionId:'").append(SecurityUtil.getUnionId()).append("'})"); - } else if (StrUtil.isNotBlank(unionId)) { - sql.append(".where({unionId:'").append(unionId).append("'})"); - } - sql.append(".skip(").append(skip).append(")"); - sql.append(".limit(").append(pageSize).append(")"); - sql.append(".get()"); - try { - JSONObject jsonObject = weAppCloudUtil.request(WeAppCloudUtil.CRUD.QUERY, sql.toString()); - List data = jsonObject.getJSONArray("data").stream() - .map(v -> JSONUtil.toBean((String) v, FitnessWalkActivity.class)) - .collect(Collectors.toList()); - JSONObject pager = jsonObject.getJSONObject("pager"); - Pagination pagination = new Pagination(pageNumber, pageSize, pager.getInt("Total"), data); - return Result.success().addData(pagination); - } catch (Exception e) { - return Result.error(e.getMessage()); - } - + @ApiOperation("获取活动数据") + public Result pageData(@Param("year") Integer year, @Param("unionId") String unionId, @Param("searchName") String searchName, @Param("searchKeyword") String searchKeyword, @Param("pageNumber") int pageNumber, @Param("pageSize") int pageSize, @Param("pageOrderName") String pageOrderName, @Param("pageOrderBy") String pageOrderBy) { + return fitnessWalkActivityService.pageData(year, unionId, searchName, searchKeyword, pageNumber, pageSize, pageOrderName, pageOrderBy); } - + /** + * 获取当前年份关联活动下拉数据。 + * 下拉数据统一由 service 生成,避免页面自己拼名称或数量导致规则不一致。 + * @return Result,code为0表示成功,data为业务结果,msg为操作说明 + */ @At - @ApiOperation("添加活动") - @SLog(tag = "健步走-活动管理", msg = "添加活动") @SaCheckPermission("fitnessWalk.activityManage") + public Result relationActivityOptions() { + return Result.success(fitnessWalkActivityService.getCurrentYearRelationActivityOptions()); + } + + /** + * 获取福利项目下拉数据。 + * 活动页面统一通过当前年份加载福利项目,避免前端直接跨模块取数。 + * @param year 四位查询年度 + * @return Result,code为0表示成功,data为业务结果,msg为操作说明 + */ + @At + @SaCheckPermission("fitnessWalk.activityManage") + public Result welfareProjectOptions(@Param("year") Integer year) { + return Result.success(fitnessWalkActivityService.getWelfareProjectOptions(year)); + } + + /** + * 创建云端活动并登记抽奖、连续创建任务和活动缓存。 + * + * @param data 兼容原活动提交参数,活动配置以fitnessWalkActivity为准 + * @param fitnessWalkActivity 页面提交的活动配置,包含模式、时间、点位和抽奖规则 + * @param tempFile 活动封面上传文件,不更换时传空 + * @param stepFiles 计步展示图片文件数组,不更换时传空 + * @param certificateTempFile 完赛证书背景文件,不更换时传空 + * @return Result,code为0表示成功,data承载创建云端活动并登记抽奖、连续创建任务和活动缓存结果,失败说明见msg + */ + @At @AdaptBy(type = UploadAdaptor.class, args = {"ioc:fileUpload"}) - public Result doAdd(@Param("fitnessWalkActivity") FitnessWalkActivity fitnessWalkActivity, + @SaCheckPermission("fitnessWalk.activityManage") + @Aop(TransAop.READ_COMMITTED) + public Result doAdd(String data, @Param("fitnessWalkActivity") FitnessWalkActivity fitnessWalkActivity, @Param("file") TempFile tempFile, @Param("stepFiles") TempFile[] stepFiles, @Param("certificateFile") TempFile certificateTempFile) { - try { - //设置时间 - if ("punch".equals(fitnessWalkActivity.getActivityModel())) { - fitnessWalkActivity.setApplyStartTime(fitnessWalkActivity.getApplyTime()[0]); - fitnessWalkActivity.setApplyEndTime(fitnessWalkActivity.getApplyTime()[1]); - } - fitnessWalkActivity.setStartTime(fitnessWalkActivity.getTime()[0]); - fitnessWalkActivity.setEndTime(fitnessWalkActivity.getTime()[1]); - - if (tempFile != null) { - String fileId = weAppCloudUtil.uploadFile("activity/", tempFile.getFile()); - fitnessWalkActivity.setCover(fileId); - } - - if (Lang.isNotEmpty(stepFiles)) { - List stepFileIdList = new ArrayList<>(); - for (TempFile file : stepFiles) { - String fileId = weAppCloudUtil.uploadFile("activity/", file.getFile()); - stepFileIdList.add(fileId); - } - fitnessWalkActivity.setRandomPics(stepFileIdList); - } - - if (certificateTempFile != null) { - String fileId = weAppCloudUtil.uploadFile("activity/", certificateTempFile.getFile()); - fitnessWalkActivity.setCompletionCertificateCover(fileId); - } - - if (AuthUtil.hasRoleOr(RoleConstant.BRANCH_UNION_CHAIRMAN.name(), RoleConstant.BRANCH_UNION_OPERATOR.name(), RoleConstant.BRANCH_UNION_ADMIN.name()) - && !AuthUtil.hasRoleOr(RoleConstant.SYSADMIN.name(), RoleConstant.SCHOOL_UNION_ADMIN.name())) { - fitnessWalkActivity.setUnionId(SecurityUtil.getUnionId()); - } else { - fitnessWalkActivity.setUnionId(""); - } - - fitnessWalkActivity.setNote(fitnessWalkActivity.getNote().replace("\"", "'")); - StringBuilder sql = new StringBuilder(); - sql.append("db.collection('activity').add({data:").append(Json.toJson(fitnessWalkActivity)).append("})"); - - try { - JSONObject jsonObject = weAppCloudUtil.request(WeAppCloudUtil.CRUD.INSERT, sql.toString()); - fitnessWalkCommonService.addLotteryTask(fitnessWalkActivity); - fitnessWalkCommonService.addOrEditDoSaveRedis(fitnessWalkActivity); - return Result.success(); - } catch (Exception e) { - return Result.error(e.getMessage()); - } - } catch (Exception e) { - e.printStackTrace(); - return Result.error(); - } + return fitnessWalkActivityService.doAdd(data, fitnessWalkActivity, tempFile, stepFiles, certificateTempFile); } - @At @Ok("json:full") - @ApiOperation("根据Id获取健步走活动") @SaCheckPermission("fitnessWalk.activityManage") + @ApiOperation("根据Id获取健步走活动") public Result findOne(String id) { return Result.success(fitnessWalkCommonService.getActivity(id)); } + /** + * 将微信云存储文件ID转换为上传组件回显信息。 + * + * @param pic 微信云存储文件ID + * @return Result,code为0表示成功,data承载将微信云存储文件ID转换为上传组件回显信息结果,失败说明见msg + */ @At @Ok("json") - @ApiOperation("获取附件") @SaCheckPermission("fitnessWalk.activityManage") + @ApiOperation("获取附件") public Result getFile(String pic) { - try { - NutMap res = NutMap.NEW(); - String imgurl = weAppCloudUtil.httpFile(pic); - HashMap imgmap = new HashMap<>(); - imgmap.put("status", "success"); - imgmap.put("name", imgurl.substring(imgurl.lastIndexOf("/") + 1)); - imgmap.put("id", imgurl); - imgmap.put("url", imgurl); - res.setv("img", imgmap); - return Result.success().addData(res); - } catch (Exception e) { - return Result.error(); - } + return fitnessWalkActivityService.getFile(pic); } - + /** + * 更新活动配置并同步关联任务和活动缓存。 + * + * @param fitnessWalkActivity 页面提交的活动配置,包含模式、时间、点位和抽奖规则 + * @param tempFile 活动封面上传文件,不更换时传空 + * @param stepFiles 计步展示图片文件数组,不更换时传空 + * @param certificateTempFile 完赛证书背景文件,不更换时传空 + * @return Result,code为0表示成功,data承载更新活动配置并同步关联任务和活动缓存结果,失败说明见msg + */ @At - @ApiOperation("修改健步走活动") - @SaCheckPermission("fitnessWalk.activityManage") @AdaptBy(type = UploadAdaptor.class, args = {"ioc:fileUpload"}) + @SaCheckPermission("fitnessWalk.activityManage") + @Aop(TransAop.READ_COMMITTED) public Result doEdit(@Param("fitnessWalkActivity") FitnessWalkActivity fitnessWalkActivity, @Param("file") TempFile tempFile, @Param("stepFiles") TempFile[] stepFiles, @Param("certificateFile") TempFile certificateTempFile) { - //设置时间 - if ("punch".equals(fitnessWalkActivity.getActivityModel())) { - fitnessWalkActivity.setApplyStartTime(fitnessWalkActivity.getApplyTime()[0]); - fitnessWalkActivity.setApplyEndTime(fitnessWalkActivity.getApplyTime()[1]); - } - fitnessWalkActivity.setStartTime(fitnessWalkActivity.getTime()[0]); - fitnessWalkActivity.setEndTime(fitnessWalkActivity.getTime()[1]); - - if (tempFile != null) { - String fileId = weAppCloudUtil.uploadFile("activity/", tempFile.getFile()); - fitnessWalkActivity.setCover(fileId); - } - - if (Lang.isNotEmpty(stepFiles)) { - List stepFileIdList = new ArrayList<>(); - for (TempFile file : stepFiles) { - String fileId = weAppCloudUtil.uploadFile("activity/", file.getFile()); - stepFileIdList.add(fileId); - } - fitnessWalkActivity.setRandomPics(stepFileIdList); - } - - if (certificateTempFile != null) { - String fileId = weAppCloudUtil.uploadFile("activity/", certificateTempFile.getFile()); - fitnessWalkActivity.setCompletionCertificateCover(fileId); - } - - - if (AuthUtil.hasRoleOr(RoleConstant.BRANCH_UNION_CHAIRMAN.name(), RoleConstant.BRANCH_UNION_OPERATOR.name(), RoleConstant.BRANCH_UNION_ADMIN.name()) - && !AuthUtil.hasRoleOr(RoleConstant.SYSADMIN.name(), RoleConstant.SCHOOL_UNION_ADMIN.name())) { - fitnessWalkActivity.setUnionId(SecurityUtil.getUnionId()); - } else { - fitnessWalkActivity.setUnionId(""); - } - - fitnessWalkActivity.setNote(fitnessWalkActivity.getNote().replace("\"", "'")); - - StringBuilder sql = new StringBuilder(); - sql.append("db.collection('activity').doc('") - .append(fitnessWalkActivity.get_id()) - .append("').update({data:").append(Json.toJson(fitnessWalkActivity)).append("})"); - - try { - JSONObject jsonObject = weAppCloudUtil.request(WeAppCloudUtil.CRUD.UPDATE, sql.toString()); - fitnessWalkCommonService.addLotteryTask(fitnessWalkActivity); - fitnessWalkCommonService.addOrEditDoSaveRedis(fitnessWalkActivity); - return Result.success(); - } catch (Exception e) { - return Result.error(e.getMessage()); - } - } - - - @At - @ApiOperation("删除健步走活动") - @SLog(tag = "健步走-活动管理", msg = "删除活动") - @SaCheckPermission("fitnessWalk.activityManage") - public Result delete(String id) { - try { - weAppCloudUtil.request(WeAppCloudUtil.CRUD.DELETE, "db.collection('activity').doc('" + id + "').remove()"); - weAppCloudUtil.request(WeAppCloudUtil.CRUD.DELETE, "db.collection('register').where({activity_id:'" + id + "'}).remove()"); - weAppCloudUtil.request(WeAppCloudUtil.CRUD.DELETE, "db.collection('sign').where({activity_id:'" + id + "'}).remove()"); - weAppCloudUtil.request(WeAppCloudUtil.CRUD.DELETE, "db.collection('gift_voucher').where({activity_id:'" + id + "'}).remove()"); - - List sys_tasks = sysTaskService.query(Cnd.where("note", "=", id)); - if (sys_tasks != null) { - sys_tasks.forEach(v -> { - taskPlatformService.delete(v.getId(), v.getId()); - sysTaskService.delete(v.getId()); - }); - - } - return Result.success(); - } catch (Exception e) { - return Result.error(); - } + return fitnessWalkActivityService.doEdit(fitnessWalkActivity, tempFile, stepFiles, certificateTempFile); } /** - * 健身走健步模式通知提醒 + * 删除云端活动及报名、打卡、礼品券和关联任务。 * - * @param map + * @param id 待操作记录的主键ID + * @return Result,code为0表示成功,data承载删除云端活动及报名、打卡、礼品券和关联任务结果,失败说明见msg */ - private void addNotifyTask(NutMap map) { - if (map.getBoolean("sendNotify")) { - String id = map.getString("_id"); - String name = map.getString("name"); - String groupId = map.getString("groupId"); - - //提醒频率 - int notifyGap = 10; - - List sys_tasks = sysTaskService.query(Cnd.where("note", "=", id)); - if (Lang.isNotEmpty(sys_tasks)) { - sys_tasks.forEach(v -> { - taskPlatformService.delete(v.getId(), v.getId()); - sysTaskService.delete(v.getId()); - }); - } - - DateTime startTime = DateUtil.date(map.getLong("start_time")); - DateTime endTime = DateUtil.date(map.getLong("end_time")); - - Set result = new HashSet<>(); - DateTime offsetDay = DateUtil.offsetDay(startTime, notifyGap); - while (offsetDay.isBefore(endTime) && offsetDay.isAfter(new Date())) { - result.add(offsetDay); - offsetDay = DateUtil.offsetDay(offsetDay, notifyGap); - } - //活动结束前一天再添加一个日期提醒 - result.add(DateUtil.offsetDay(endTime, -1)); - - for (DateTime dateTime : result) { - Sys_task sys_task = new Sys_task(); - sys_task.setName(name); - sys_task.setNote(id); - sys_task.setJobClass("io.v.nutz.task.job.walking.WalkingNotifyJob"); - sys_task.setData(Json.toJson(Map.of("groupId", groupId))); - sys_task.setDisabled(false); - - String dateFormat = "ss mm HH dd MM ? yyyy"; - SimpleDateFormat sdf = new SimpleDateFormat(dateFormat); - String corn = sdf.format(dateTime); - sys_task.setCron(corn); - - sysTaskService.insert(sys_task); - - taskPlatformService.add(sys_task.getId(), sys_task.getId(), sys_task.getJobClass(), sys_task.getCron(), sys_task.getNote(), sys_task.getData()); - } - } + @At + @SaCheckPermission("fitnessWalk.activityManage") + @Aop(TransAop.READ_COMMITTED) + @ApiOperation("删除健步走活动") + @SLog(tag = "健步走-活动管理", msg = "删除活动") + public Result delete(String id) { + return fitnessWalkActivityService.deleteActivity(id); } + } diff --git a/src/main/java/com/budwk/app/zhgh/activity/fitnesswalk/controller/FitnessWalkLoginController.java b/src/main/java/com/budwk/app/zhgh/activity/fitnesswalk/controller/FitnessWalkLoginController.java index 0aa14a1..bff3014 100644 --- a/src/main/java/com/budwk/app/zhgh/activity/fitnesswalk/controller/FitnessWalkLoginController.java +++ b/src/main/java/com/budwk/app/zhgh/activity/fitnesswalk/controller/FitnessWalkLoginController.java @@ -1,16 +1,17 @@ package com.budwk.app.zhgh.activity.fitnesswalk.controller; +import cn.hutool.core.date.DateUtil; import cn.hutool.core.util.StrUtil; import com.budwk.app.base.result.Result; -import com.budwk.app.sys.services.SysUserService; -import org.nutz.dao.Cnd; -import org.nutz.dao.Sqls; -import org.nutz.dao.sql.Sql; +import com.budwk.app.zhgh.activity.fitnesswalk.service.FitnessWalkCommonService; +import org.nutz.aop.interceptor.ioc.TransAop; +import org.nutz.ioc.aop.Aop; import org.nutz.ioc.loader.annotation.Inject; import org.nutz.ioc.loader.annotation.IocBean; import org.nutz.lang.util.NutMap; import org.nutz.mvc.annotation.At; import org.nutz.mvc.annotation.Ok; +import java.util.Date; /** * @author: Aaron @@ -23,53 +24,60 @@ import org.nutz.mvc.annotation.Ok; public class FitnessWalkLoginController { @Inject - private SysUserService sysUserService; + private FitnessWalkCommonService fitnessWalkCommonService; + /** + * 按姓名和工号匹配小程序用户并返回单位及角色资料。 + * + * @param username 用户姓名,与工号共同匹配人员 + * @param loginname 用户工号 + * @return Result,code为0表示成功,data承载按姓名和工号匹配小程序用户并返回单位及角色资料结果,失败说明见msg + */ @At("/") - public Object index(String username, String loginname) { - try { - Sql sql = Sqls.create(""" - SELECT - u.id userid, - u.username, - u.loginname, - u.sex, - u.mobile, - u.birthday, - unit.`name` unitname, - u.unitid, - u.member, - unit.unionid, - un.unionname, - group_concat(sr.`code`) roles - FROM - sys_user u - LEFT JOIN sys_unit unit ON u.unitid = unit.id - LEFT JOIN sys_union un ON unit.unionid = un.id - LEFT JOIN sys_user_role sur ON sur.userId = u.id - LEFT JOIN sys_role sr ON sr.id = sur.roleId - $condition - """).setParam("loginname", loginname).setParam("username", username); + public Result index(String username, String loginname) { + return fitnessWalkCommonService.login(username, loginname); + } - Cnd cnd = Cnd.NEW(); - cnd.and("LOWER(u.loginname)", "=", loginname.trim().toLowerCase()); - cnd.and("LOWER(u.username)", "=", username.trim().toLowerCase()); - sql.setCondition(cnd); - sql.setCallback(Sqls.callback.map()); - sysUserService.dao().execute(sql); - NutMap user = (NutMap) sql.getResult(); - if (StrUtil.isBlank(user.getString("userid"))) { - return Result.error().addMsg("系统查询不到您的信息,请与工会管理员联系!"); + /** + * 补全健步走用户基础资料。 + * + * @param userId 用户ID,传登录接口返回的 userid + * @param sex 性别,只能传“男”或“女” + * @param birthday 出生日期,格式为 yyyy-MM-dd,且不能晚于当前日期 + * @return Result,data 中包含最终生效的 sex 和 birthday + */ + @At("/completeUserInfo") + @Aop(TransAop.READ_COMMITTED) + public Result completeUserInfo(String userId, String sex, String birthday) { + if (StrUtil.hasBlank(userId, sex, birthday)) { + return Result.error().addMsg("请完整填写性别和出生日期!"); + } + if (!"男".equals(sex) && !"女".equals(sex)) { + return Result.error().addMsg("性别参数不正确!"); + } + + Date birthdayDate; + try { + birthdayDate = DateUtil.parse(birthday, "yyyy-MM-dd"); + if (!birthday.equals(DateUtil.format(birthdayDate, "yyyy-MM-dd"))) { + return Result.error().addMsg("出生日期格式不正确!"); } -// if(user.getInt("member",0)==0){ -// return Result.error().addMsg("您没有权限参加本次健步行,请与工会管理员联系!"); -// } - /*if (user.getInt("member", 0) != 1 && !user.getString("sex", "").equals("女")) { - return Result.error().addMsg("您没有权限参加本次健步行,请与工会管理员联系!"); - }*/ - return Result.success().addData(user); } catch (Exception e) { - return Result.error(); + return Result.error().addMsg("出生日期格式不正确!"); + } + + if (birthdayDate.after(new Date())) { + return Result.error().addMsg("出生日期不能晚于当前日期!"); + } + + try { + NutMap userInfo = fitnessWalkCommonService.completeUserInfo(userId, sex, birthdayDate); + if (userInfo == null) { + return Result.error().addMsg("系统查询不到您的信息,请重新登录!"); + } + return Result.success().addData(userInfo); + } catch (Exception e) { + return Result.error().addMsg("个人信息保存失败,请稍后重试!"); } } } diff --git a/src/main/java/com/budwk/app/zhgh/activity/fitnesswalk/controller/FitnessWalkPunchLotteryController.java b/src/main/java/com/budwk/app/zhgh/activity/fitnesswalk/controller/FitnessWalkPunchLotteryController.java index d1fbebd..35637a1 100644 --- a/src/main/java/com/budwk/app/zhgh/activity/fitnesswalk/controller/FitnessWalkPunchLotteryController.java +++ b/src/main/java/com/budwk/app/zhgh/activity/fitnesswalk/controller/FitnessWalkPunchLotteryController.java @@ -1,258 +1,95 @@ package com.budwk.app.zhgh.activity.fitnesswalk.controller; -import cn.hutool.core.util.StrUtil; -import cn.hutool.json.JSONObject; -import cn.hutool.json.JSONUtil; import com.budwk.app.base.result.Result; -import com.budwk.app.sys.models.Sys_user; -import com.budwk.app.zhgh.activity.fitnesswalk.model.FitnessWalkActivity; -import com.budwk.app.zhgh.activity.fitnesswalk.model.FitnessWalkPunchLottery; -import com.budwk.app.zhgh.activity.fitnesswalk.service.FitnessWalkCommonService; -import com.budwk.app.zhgh.activity.fitnesswalk.utils.WeAppCloudUtil; +import com.budwk.app.zhgh.activity.fitnesswalk.service.FitnessWalkPunchLotteryService; import io.swagger.annotations.ApiOperation; -import org.nutz.dao.Chain; -import org.nutz.dao.Cnd; -import org.nutz.integration.jedis.RedisService; +import org.nutz.aop.interceptor.ioc.TransAop; +import org.nutz.ioc.aop.Aop; import org.nutz.ioc.loader.annotation.Inject; import org.nutz.ioc.loader.annotation.IocBean; -import org.nutz.lang.Lang; -import org.nutz.lang.random.R; import org.nutz.mvc.annotation.At; import org.nutz.mvc.annotation.Ok; - import java.util.*; -import java.util.concurrent.locks.ReentrantLock; -import java.util.stream.Collectors; /** * 打卡抽奖 */ @IocBean @Ok("json") -@ApiOperation("健步走打卡抽奖") @At("/platform/fitnessWalk/punchLottery") public class FitnessWalkPunchLotteryController { - private final ReentrantLock lock = new ReentrantLock(true); - @Inject - private FitnessWalkCommonService fitnessWalkCommonService; - - @Inject - private WeAppCloudUtil weAppCloudUtil; - - @Inject - private RedisService redisService; + private FitnessWalkPunchLotteryService fitnessWalkPunchLotteryService; @At("") @Ok("beetl:/platform/zhgh/activity/fitnesswalk/punchLottery.html") public void index() { } - /** - * 打卡完成之后抽奖 + * 按源版奖池和报名人数分配打卡抽奖结果。 * - * @param activityId 活动Id - * @param userId 用户Id - * @return + * @param activityId 健步走云端活动ID + * @param userId 登录接口返回的用户ID + * @return Result,code为0表示成功,data承载按源版奖池和报名人数分配打卡抽奖结果结果,失败说明见msg */ @At @ApiOperation("打卡模式的抽奖") public Result judgeWinningToPunch(String activityId, String userId) { - try { - if (StrUtil.isBlank(activityId) && StrUtil.isBlank(userId)) { - return Result.error("参数错误"); - } - - lock.lock(); - int winingCount = fitnessWalkCommonService.dao().count(FitnessWalkPunchLottery.class, Cnd.where("activityId", "=", activityId).and("userId", "=", userId)); - if (winingCount > 0) { - return null; - } - - // 把中奖的用户直接存到redis里面去 - String redisKey = this.getClass().getName() + "#judgeWinningToPunch#activityid:" + activityId + "#PRIZELIST:"; - String getPrizeIndexesKey = this.getClass().getName() + "#judgeWinningToPunch#activityid:" + activityId + "#GETPRIZEINDEXES:"; - -// redisService.del(getPrizeIndexesKey); - // 获取奖品列表 - List punchLotteryPrizes = new ArrayList<>(); - String punchLotteryPrizesJson = redisService.get(redisKey); - if (StrUtil.isNotBlank(punchLotteryPrizesJson)) { - punchLotteryPrizes = JSONUtil.toList(punchLotteryPrizesJson, FitnessWalkActivity.PunchLotteryPrize.class); - } else { - JSONObject prizeJsonObject = weAppCloudUtil.request(WeAppCloudUtil.CRUD.QUERY, "db.collection('activity').doc('%s').field({punchLotteryPrizes: true}).get()".formatted(activityId)); - List prizeList = JSONUtil.parseObj((String) prizeJsonObject.getJSONArray("data").get(0)).getJSONArray("punchLotteryPrizes").toList(FitnessWalkActivity.PunchLotteryPrize.class); - redisService.set(redisKey, JSONUtil.toJsonStr(prizeList)); - punchLotteryPrizes = prizeList; - } - - List winningUserIndexList = null; - //奖项,抽取多少个 - int sum = punchLotteryPrizes.stream().mapToInt(v -> Integer.parseInt(v.getNum())).sum(); - //判断是否有key,可以获得奖项的下标数组 - if (!redisService.exists(getPrizeIndexesKey)) { - JSONObject jsonObject = weAppCloudUtil.request(WeAppCloudUtil.CRUD.QUERY, "db.collection('register').where({activity_id:'%s'}).count()".formatted(activityId)); - JSONObject pager = jsonObject.getJSONObject("pager"); - String total = pager.getStr("Total"); - //报名人数的一半,从这里的人中取数(最多抽取人数) - Integer lotteryNum = (int) Math.ceil(Double.parseDouble(total) / 2); - winningUserIndexList = generateRandomNumber(1, lotteryNum, sum); - redisService.set(getPrizeIndexesKey, JSONUtil.toJsonStr(winningUserIndexList)); - } else { - String data = redisService.get(getPrizeIndexesKey); - winningUserIndexList = JSONUtil.toList(data, Integer.class); - } - - List punchLotteryList = fitnessWalkCommonService.dao().query(FitnessWalkPunchLottery.class, Cnd.where("activityId", "=", activityId)); - - Sys_user user = fitnessWalkCommonService.dao().fetch(Sys_user.class, Cnd.where("id", "=", userId)); - - FitnessWalkPunchLottery punchLottery = new FitnessWalkPunchLottery(); - punchLottery.setId(R.UU32()); - punchLottery.setActivityId(activityId); - punchLottery.setUserId(userId); - punchLottery.setLoginName(user.getLoginname()); - punchLottery.setUserName(user.getUsername()); - punchLottery.setWinDate(new Date()); - punchLottery.setIsRead(false); - punchLottery.setIsWin(false); - punchLottery.setIsExchange(false); - - if (winningUserIndexList.contains((punchLotteryList.size()))) { - - //奖项map - Map prizeMap = punchLotteryPrizes.stream().collect(Collectors.toMap(FitnessWalkActivity.PunchLotteryPrize::getValue, FitnessWalkActivity.PunchLotteryPrize::getName)); - - //奖项池 - List prizeIdList = new ArrayList<>(); - - punchLotteryPrizes.forEach(v -> { - for (int i = 0; i < Integer.parseInt(v.getNum()); i++) { - prizeIdList.add(v.getValue()); - } - }); - - //已经中奖的奖项ID集合 - List winPrizeIdList = punchLotteryList.stream().filter(FitnessWalkPunchLottery::getIsWin).map(FitnessWalkPunchLottery::getPrizeId).toList(); - winPrizeIdList.forEach(prizeIdList::remove); - - Random random = new Random(); - if (Lang.isNotEmpty(prizeIdList.size())) { - int index = random.nextInt(prizeIdList.size()); - - // 获取随机元素 - String winingPrizeId = prizeIdList.get(index); - String winingPrizeName = prizeMap.get(winingPrizeId); - punchLottery.setPrizeName(winingPrizeName); - punchLottery.setPrizeId(winingPrizeId); - punchLottery.setIsWin(true); - - } - - } - - fitnessWalkCommonService.dao().insert(punchLottery); - if (punchLottery.getIsWin()) { - return Result.success().addData(punchLottery.getPrizeId()); - } else { - return Result.success().addData(null); - } - } catch (Exception e) { - e.printStackTrace(); - return null; - } finally { - if (lock.isLocked()) { - lock.unlock(); - } - } + return fitnessWalkPunchLotteryService.judgeWinningToPunch(activityId, userId); } + /** + * 查询用户在指定活动中的打卡抽奖记录。 + * + * @param activityId 健步走云端活动ID + * @param userId 登录接口返回的用户ID + * @return Result,code为0表示成功,data承载查询用户在指定活动中的打卡抽奖记录结果,失败说明见msg + */ @At @ApiOperation("获取中奖记录") public Result getLotteryRecord(String activityId, String userId) { - List list = fitnessWalkCommonService.dao().query(FitnessWalkPunchLottery.class, Cnd.where("activityId", "=", activityId) - .and("userId", "=", userId)); - return Result.success(list); + return fitnessWalkPunchLotteryService.getLotteryRecord(activityId, userId); } - /** - * 兑奖 + * 核销奖品并记录兑换时间,重复核销返回状态2。 * - * @param id - * @return + * @param id 待操作记录的主键ID + * @return Result,code为0表示成功,data承载核销奖品并记录兑换时间,重复核销返回状态2结果,失败说明见msg */ @At + @Aop(TransAop.READ_COMMITTED) @ApiOperation("兑奖") public Result doExchange(String id) { - - int count = fitnessWalkCommonService.dao().count(FitnessWalkPunchLottery.class, Cnd.where("id", "=", id).and("isExchange", "=", 1)); - //返回2 "该二维码已兑奖!" - if (count > 0) { - return Result.success(2); - } - - int update = fitnessWalkCommonService.dao().update(FitnessWalkPunchLottery.class, - Chain.make("isExchange", 1).add("exchangeDate", new Date()), - Cnd.where("id", "=", id)); - return update > 0 ? Result.success() : Result.error(); + return fitnessWalkPunchLotteryService.doExchange(id); } - /** - * 改为已读 + * 将用户在指定活动中的抽奖记录标记为已读。 * - * @param activityId - * @param userId - * @return + * @param activityId 健步走云端活动ID + * @param userId 登录接口返回的用户ID + * @return Result,code为0表示成功,data承载将用户在指定活动中的抽奖记录标记为已读结果,失败说明见msg */ @At + @Aop(TransAop.READ_COMMITTED) @ApiOperation("改为已读") public Result doReadLottery(String activityId, String userId) { - fitnessWalkCommonService.dao().update(FitnessWalkPunchLottery.class, Chain.make("isRead", 1), - Cnd.where("userId", "=", userId).and("activityId", "=", activityId)); - return Result.success(); + return fitnessWalkPunchLotteryService.doReadLottery(activityId, userId); } - /** - * 查看获奖人员 + * 查询指定打卡活动中已中奖的人员。 * - * @return + * @param activityId 健步走云端活动ID + * @return Result,code为0表示成功,data承载查询指定打卡活动中已中奖的人员结果,失败说明见msg */ @At @ApiOperation("查看获奖人员") public Result getPunchWining(String activityId) { - List punchLotteryList = fitnessWalkCommonService.dao().query(FitnessWalkPunchLottery.class, - Cnd.where("isWin", "=", 1).and("activityId", "=", activityId)); - return Result.success(punchLotteryList); + return fitnessWalkPunchLotteryService.getPunchWining(activityId); } - - /** - * 抽奖方法的工具类,生成随机数,该随机数为中奖人 - * - * @param minNum 最小值 - * @param maxNum 最多抽取多少人 - * @param count 抽几个 - * @return - */ - public List generateRandomNumber(Integer minNum, Integer maxNum, Integer count) { - Set randomNumbers = new HashSet<>(); - Random rand = new Random(); - //如果是 抽奖人数小于了 奖品数量,会陷入死循环 - if (maxNum <= count) { - for (int i = 0; i <= maxNum; i++) { - randomNumbers.add(i + 1); - } - } else { - while (randomNumbers.size() < count) { - int randomNum = rand.nextInt(maxNum - minNum + 1) + minNum; - randomNumbers.add(randomNum); - } - } - return new ArrayList<>(randomNumbers); - } } diff --git a/src/main/java/com/budwk/app/zhgh/activity/fitnesswalk/controller/FitnessWalkPunchStatisticsController.java b/src/main/java/com/budwk/app/zhgh/activity/fitnesswalk/controller/FitnessWalkPunchStatisticsController.java index a26ffc6..a5fb29a 100644 --- a/src/main/java/com/budwk/app/zhgh/activity/fitnesswalk/controller/FitnessWalkPunchStatisticsController.java +++ b/src/main/java/com/budwk/app/zhgh/activity/fitnesswalk/controller/FitnessWalkPunchStatisticsController.java @@ -1,20 +1,12 @@ package com.budwk.app.zhgh.activity.fitnesswalk.controller; -import cn.afterturn.easypoi.excel.ExcelExportUtil; -import cn.afterturn.easypoi.excel.entity.ExportParams; -import cn.afterturn.easypoi.excel.entity.params.ExcelExportEntity; import cn.dev33.satoken.annotation.SaCheckPermission; -import cn.hutool.json.JSONObject; +import com.alibaba.fastjson.JSONObject; import com.budwk.app.base.result.Result; -import com.budwk.app.base.service.BaseService; -import com.budwk.app.base.utils.CommonDownloadUtil; -import com.budwk.app.sys.models.Sys_union; import com.budwk.app.zhgh.activity.fitnesswalk.contants.FitnessWalkMode; import com.budwk.app.zhgh.activity.fitnesswalk.service.FitnessWalkCommonService; import com.budwk.app.zhgh.activity.fitnesswalk.service.FitnessWalkPunchStatisticsService; -import com.budwk.app.zhgh.activity.fitnesswalk.utils.WeAppCloudUtil; import io.swagger.annotations.ApiOperation; -import org.apache.poi.ss.usermodel.Workbook; import org.nutz.dao.Dao; import org.nutz.ioc.loader.annotation.Inject; import org.nutz.ioc.loader.annotation.IocBean; @@ -22,34 +14,23 @@ import org.nutz.lang.util.NutMap; import org.nutz.mvc.annotation.At; import org.nutz.mvc.annotation.Ok; import org.nutz.mvc.annotation.Param; - -import javax.servlet.http.HttpServletResponse; -import java.io.IOException; -import java.util.ArrayList; import java.util.List; -import java.util.Optional; +import javax.servlet.http.HttpServletResponse; /** * 打卡统计 */ @IocBean @Ok("json") -@ApiOperation("健步走打卡统计") @At("/platform/fitnessWalk/punchStatistics") public class FitnessWalkPunchStatisticsController { @Inject private Dao dao; - @Inject - private BaseService baseService; - @Inject private FitnessWalkCommonService fitnessWalkCommonService; - @Inject - private WeAppCloudUtil weAppCloudUtil; - @Inject private FitnessWalkPunchStatisticsService fitnessWalkPunchStatisticsService; @@ -62,130 +43,97 @@ public class FitnessWalkPunchStatisticsController { /** * 分工会统计 * - * @param activityId - * @param unionId - * @return + * @param activityId 健步走云端活动ID + * @param unionId 分工会ID,空值表示不追加组织筛选 + * @return Result,code为0表示成功,data为业务结果,msg为操作说明 + * @param mainActivityId 主活动ID或relation:关联项目ID + * @param subActivityId 分活动ID,空值表示汇总主活动 */ @At @Ok("json:full") + @SaCheckPermission("fitnessWalk.punchStatistics") @ApiOperation("打卡记录统计") - @SaCheckPermission("fitnessWalk.punchStatistics") - public Result pageData(String activityId, String unionId) { - List list = fitnessWalkPunchStatisticsService.unionRegCheckInNum(activityId, unionId); + public Result pageData(String mainActivityId, String subActivityId, String activityId, String unionId) { + List list = fitnessWalkPunchStatisticsService.unionRegCheckInNum(mainActivityId, subActivityId, activityId, unionId); return Result.success(list); } @At + @SaCheckPermission("fitnessWalk.punchStatistics") @ApiOperation("获取活动列表") - @SaCheckPermission("fitnessWalk.punchStatistics") public Result getCascadersActivity(@Param("year") int year) { - return Result.success().addData(fitnessWalkCommonService.cascaderActivity(year, FitnessWalkMode.PUNCH)); + return Result.success(fitnessWalkCommonService.cascaderActivity(year, FitnessWalkMode.PUNCH)); } @At @Ok("json:full") + @SaCheckPermission("fitnessWalk.punchStatistics") @ApiOperation("根据工会查询获取报名人员") - @SaCheckPermission("fitnessWalk.punchStatistics") - public Result unionRegistrationData(String activityId, String unionId) { - List list = fitnessWalkPunchStatisticsService.unionRegistrationData(activityId, unionId); + public Result unionRegistrationData(String mainActivityId, String subActivityId, String activityId, String unionId) { + List list = fitnessWalkPunchStatisticsService.unionRegistrationData(mainActivityId, subActivityId, activityId, unionId); return Result.success(list); } @At @Ok("json:full") - @ApiOperation("根据工会查询获取打卡人员") @SaCheckPermission("fitnessWalk.punchStatistics") - public Result unionCheckInData(String activityId, String unionId) { - List list = fitnessWalkPunchStatisticsService.unionCheckInData(activityId, unionId); + @ApiOperation("根据工会查询获取打卡人员") + public Result unionCheckInData(String mainActivityId, String subActivityId, String activityId, String unionId) { + List list = fitnessWalkPunchStatisticsService.unionCheckInData(mainActivityId, subActivityId, activityId, unionId); return Result.success(list); } - + /** + * 导出所选主分活动的分工会报名与打卡人数。 + * + * @param response HTTP响应,用于写入XSSF格式Excel文件流 + * @param mainActivityId 主活动ID或relation:关联项目ID + * @param subActivityId 分活动ID,空值表示主活动汇总 + * @param activityId 健步走云端活动ID + * 无返回对象,通过response写出Excel文件流。 + */ @At @Ok("void") + @SaCheckPermission("fitnessWalk.punchStatistics") @ApiOperation("导出各分工会报名人数、打卡人数") - @SaCheckPermission("fitnessWalk.punchStatistics") - public void exportUnionRegCheckInNum(HttpServletResponse response, String activityId) { - List list = fitnessWalkPunchStatisticsService.unionRegCheckInNum(activityId, null); - List entities = new ArrayList<>(); - entities.add(new ExcelExportEntity("分工会", "unionname", 20)); - entities.add(new ExcelExportEntity("报名人数", "regCount", 20)); - entities.add(new ExcelExportEntity("打卡人数", "checkInCount", 20)); - - try { - Workbook workbook = ExcelExportUtil.exportExcel(new ExportParams(), entities, list); - workbook.write(response.getOutputStream()); - - CommonDownloadUtil.download("各分工会统计人数.xlsx", workbook, response); - } catch (IOException e) { - throw new RuntimeException(e); - } + public void exportUnionRegCheckInNum(HttpServletResponse response, String mainActivityId, String subActivityId, String activityId) { + fitnessWalkPunchStatisticsService.exportUnionRegCheckInNum(response, mainActivityId, subActivityId, activityId); } - + /** + * 导出所选活动和分工会的报名人员明细。 + * + * @param response HTTP响应,用于写入XSSF格式Excel文件流 + * @param mainActivityId 主活动ID或relation:关联项目ID + * @param subActivityId 分活动ID,空值表示主活动汇总 + * @param activityId 健步走云端活动ID + * @param unionId 分工会ID,空值表示不追加筛选,已有数据权限仍生效 + * 无返回对象,通过response写出Excel文件流。 + */ @At @Ok("void") + @SaCheckPermission("fitnessWalk.punchStatistics") @ApiOperation("导出某个分工会的报名人员") - @SaCheckPermission("fitnessWalk.punchStatistics") - public void exportRegUsers(HttpServletResponse response, String activityId, String unionId) { - Sys_union union = dao.fetch(Sys_union.class, unionId); - String unionName = Optional.ofNullable(union).map(Sys_union::getName).orElse(null); - - List list = fitnessWalkPunchStatisticsService.unionRegistrationData(activityId, unionId); - List entities = new ArrayList<>(); - entities.add(new ExcelExportEntity("工号", "loginname", 20)); - entities.add(new ExcelExportEntity("姓名", "username", 20)); - entities.add(new ExcelExportEntity("单位", "unitname", 20)); - entities.add(new ExcelExportEntity("手机号", "mobile", 20)); - entities.add(new ExcelExportEntity("报名时间", "register_time_str", 20)); - - try { - Workbook workbook = ExcelExportUtil.exportExcel(new ExportParams(), entities, list); - workbook.write(response.getOutputStream()); - CommonDownloadUtil.download("分工会报名人员.xlsx", workbook, response); - } catch (IOException e) { - throw new RuntimeException(e); - } + public void exportRegUsers(HttpServletResponse response, String mainActivityId, String subActivityId, String activityId, String unionId) { + fitnessWalkPunchStatisticsService.exportRegUsers(response, mainActivityId, subActivityId, activityId, unionId); } - + /** + * 导出所选活动和分工会的打卡点位完成明细。 + * + * @param response HTTP响应,用于写入XSSF格式Excel文件流 + * @param mainActivityId 主活动ID或relation:关联项目ID + * @param subActivityId 分活动ID,空值表示主活动汇总 + * @param activityId 健步走云端活动ID + * @param unionId 分工会ID,空值表示不追加筛选,已有数据权限仍生效 + * 无返回对象,通过response写出Excel文件流。 + */ @At @Ok("void") - @ApiOperation("导出某个分工会的打卡人员") @SaCheckPermission("fitnessWalk.punchStatistics") - public void exportCheckInUsers(HttpServletResponse response, String activityId, String unionId) { - Sys_union union = dao.fetch(Sys_union.class, unionId); - String unionName = Optional.ofNullable(union).map(Sys_union::getName).orElse(null); - - List list = fitnessWalkPunchStatisticsService.unionCheckInData(activityId, unionId); - - for (JSONObject jsonObject : list) { - Boolean used = jsonObject.getBool("used"); - if (used == null) { - jsonObject.put("used", "未取得"); - } else if (used) { - jsonObject.put("used", "已领取"); - } else { - jsonObject.put("used", "未领取"); - } - } - - List entities = new ArrayList<>(); - entities.add(new ExcelExportEntity("工号", "loginname", 20)); - entities.add(new ExcelExportEntity("姓名", "username", 20)); - entities.add(new ExcelExportEntity("单位", "unitname", 20)); - entities.add(new ExcelExportEntity("手机号", "mobile", 20)); - entities.add(new ExcelExportEntity("点位总数", "pts_size", 20)); - entities.add(new ExcelExportEntity("已打卡点位数", "sign_size", 20)); - entities.add(new ExcelExportEntity("礼品券", "used", 20)); - - try { - Workbook workbook = ExcelExportUtil.exportExcel(new ExportParams(), entities, list); - workbook.write(response.getOutputStream()); - CommonDownloadUtil.download(unionName + "分工会报名人员.xlsx", workbook, response); - } catch (IOException e) { - throw new RuntimeException(e); - } + @ApiOperation("导出某个分工会的打卡人员") + public void exportCheckInUsers(HttpServletResponse response, String mainActivityId, String subActivityId, String activityId, String unionId) { + fitnessWalkPunchStatisticsService.exportCheckInUsers(response, mainActivityId, subActivityId, activityId, unionId); } // /** @@ -203,45 +151,38 @@ public class FitnessWalkPunchStatisticsController { // // } - + /** + * 导出所选活动中尚未取得礼品券的报名人员。 + * + * @param response HTTP响应,用于写入XSSF格式Excel文件流 + * @param mainActivityId 主活动ID或relation:关联项目ID + * @param subActivityId 分活动ID,空值表示主活动汇总 + * @param activityId 健步走云端活动ID + * 无返回对象,通过response写出Excel文件流。 + */ @At @Ok("void") - @ApiOperation("导出未领取得奖品券用户") @SaCheckPermission("fitnessWalk.punchStatistics") - public void exportNotGetGiftVoucherUsers(HttpServletResponse response, String activityId) { - List list = fitnessWalkPunchStatisticsService.exportNotGetGiftVoucherUsers(activityId); - List entities = new ArrayList<>(); - entities.add(new ExcelExportEntity("工号", "loginname", 20)); - entities.add(new ExcelExportEntity("姓名", "username", 20)); - entities.add(new ExcelExportEntity("单位", "unitname", 20)); - entities.add(new ExcelExportEntity("手机号", "mobile", 20)); - try { - Workbook workbook = ExcelExportUtil.exportExcel(new ExportParams(), entities, list); - workbook.write(response.getOutputStream()); - CommonDownloadUtil.download("未领取得奖品券人员.xlsx", workbook, response); - } catch (IOException e) { - throw new RuntimeException(e); - } + @ApiOperation("导出未领取得奖品券用户") + public void exportNotGetGiftVoucherUsers(HttpServletResponse response, String mainActivityId, String subActivityId, String activityId) { + fitnessWalkPunchStatisticsService.exportNotGetGiftVoucherUsers(response, mainActivityId, subActivityId, activityId); } + /** + * 导出所选活动中尚未使用礼品券的人员。 + * + * @param response HTTP响应,用于写入XSSF格式Excel文件流 + * @param mainActivityId 主活动ID或relation:关联项目ID + * @param subActivityId 分活动ID,空值表示主活动汇总 + * @param activityId 健步走云端活动ID + * 无返回对象,通过response写出Excel文件流。 + */ @At @Ok("void") - @ApiOperation("导出未使用奖品券用户") @SaCheckPermission("fitnessWalk.punchStatistics") - public void exportNotUseGiftVoucherUsers(HttpServletResponse response, String activityId) { - List list = fitnessWalkPunchStatisticsService.exportNotUseGiftVoucherUsers(activityId); - List entities = new ArrayList<>(); - entities.add(new ExcelExportEntity("工号", "loginname", 20)); - entities.add(new ExcelExportEntity("姓名", "username", 20)); - entities.add(new ExcelExportEntity("单位", "unitname", 20)); - entities.add(new ExcelExportEntity("手机号", "mobile", 20)); - try { - Workbook workbook = ExcelExportUtil.exportExcel(new ExportParams(), entities, list); - workbook.write(response.getOutputStream()); - CommonDownloadUtil.download("导出未使用礼品券用户.xlsx", workbook, response); - } catch (IOException e) { - throw new RuntimeException(e); - } + @ApiOperation("导出未使用奖品券用户") + public void exportNotUseGiftVoucherUsers(HttpServletResponse response, String mainActivityId, String subActivityId, String activityId) { + fitnessWalkPunchStatisticsService.exportNotUseGiftVoucherUsers(response, mainActivityId, subActivityId, activityId); } } diff --git a/src/main/java/com/budwk/app/zhgh/activity/fitnesswalk/controller/FitnessWalkStepManageController.java b/src/main/java/com/budwk/app/zhgh/activity/fitnesswalk/controller/FitnessWalkStepManageController.java index acbfd73..ed4f299 100644 --- a/src/main/java/com/budwk/app/zhgh/activity/fitnesswalk/controller/FitnessWalkStepManageController.java +++ b/src/main/java/com/budwk/app/zhgh/activity/fitnesswalk/controller/FitnessWalkStepManageController.java @@ -1,66 +1,36 @@ package com.budwk.app.zhgh.activity.fitnesswalk.controller; -import cn.hutool.core.date.DateTime; -import cn.hutool.core.date.DateUnit; -import cn.hutool.core.date.DateUtil; -import cn.hutool.core.util.StrUtil; import com.budwk.app.base.annotation.SLog; import com.budwk.app.base.result.Result; -import com.budwk.app.base.service.BaseService; -import com.budwk.app.zhgh.activity.fitnesswalk.model.FitnessWalkActivity; -import com.budwk.app.zhgh.activity.fitnesswalk.model.FitnessWalkStep; -import com.budwk.app.zhgh.activity.fitnesswalk.service.FitnessWalkCommonService; -import com.budwk.app.zhgh.activity.fitnesswalk.utils.WeAppCloudUtil; -import com.budwk.app.zhgh.dayofficework.integral.model.IntegralDetail; +import com.budwk.app.zhgh.activity.fitnesswalk.service.FitnessWalkStepManageService; import io.swagger.annotations.ApiOperation; import org.nutz.aop.interceptor.ioc.TransAop; -import org.nutz.dao.Cnd; import org.nutz.dao.Dao; -import org.nutz.dao.Sqls; -import org.nutz.dao.sql.Sql; -import org.nutz.integration.jedis.RedisService; import org.nutz.ioc.aop.Aop; import org.nutz.ioc.loader.annotation.Inject; import org.nutz.ioc.loader.annotation.IocBean; -import org.nutz.json.Json; -import org.nutz.lang.Lang; -import org.nutz.lang.util.NutMap; import org.nutz.mvc.annotation.At; import org.nutz.mvc.annotation.Ok; - -import java.math.BigDecimal; -import java.math.RoundingMode; -import java.text.DecimalFormat; import java.util.*; -import java.util.function.BinaryOperator; -import java.util.function.Function; -import java.util.stream.Collectors; @IocBean @Ok("json") -@ApiOperation("健步走步数管理") @At("/platform/fitnessWalk/stepManage") public class FitnessWalkStepManageController { @Inject - private Dao dao; - @Inject - private BaseService baseService; - @Inject - private RedisService redisService; - @Inject - private WeAppCloudUtil weAppCloudUtil; - @Inject - private FitnessWalkCommonService fitnessWalkCommonService; + private FitnessWalkStepManageService fitnessWalkStepManageService; + @Inject + private Dao dao; /** - * 用户上传一个近31天的步数 + * 合并微信运动最近30天步数,按每天最大步数保存并计算积分。 * - * @param activityId 活动id - * @param userId 用户id - * @param monthSteps 步数(当天往前推30天) - * @return + * @param activityId 健步走云端活动ID + * @param userId 登录接口返回的用户ID + * @param monthSteps 微信运动步数JSON数组,每项包含step及秒级timestamp + * @return Result,code为0表示成功,data承载合并微信运动最近30天步数,按每天最大步数保存并计算积分结果,失败说明见msg */ @At @Ok("json:full") @@ -68,336 +38,65 @@ public class FitnessWalkStepManageController { @SLog(tag = "健步走步数管理", msg = "微信步数上传") @ApiOperation("上传微信步数,再根据是否开启了积分,自动将步数转化成积分") public Result updateStepMonth(String activityId, String userId, String monthSteps) { - try { - if (StrUtil.isBlank(userId) || StrUtil.isBlank(monthSteps) || StrUtil.isBlank(activityId)) { - return Result.error("必要参数未传递"); - } - - // 健步走的活动数据 - FitnessWalkActivity activity = fitnessWalkCommonService.getActivity(activityId); - // 是否启用了积分 - boolean isPoints = activity.getIsPoints() != null && activity.getIsPoints(); - - // 使用对象解构获取活动参数 - final Integer standSteps = activity.getLotteryQualificationSteps(); - final Integer standPoints = activity.getLotteryQualificationPoints(); - final Integer otherSteps = activity.getOtherSteps(); - final Integer otherPoints = activity.getOtherPoints(); - final Integer maxPoints = activity.getMaxPoints(); - - String integralNotes = activity.getMasterActivityName() + "所获积分"; - - // 获取到的用户的步数数据 - List steps = Json.fromJsonAsList(NutMap.class, monthSteps); - - //判断数据是否完整 微信运动会在晚上10点多进行步数的更新 会导致31天前的步数获取变为0 所以这里去除掉第一个元素 即为31天前的数据 每次只保存最近30天的数据 - if (Lang.isNotEmpty(steps) && steps.size() == 31) { - steps.remove(0); - } - - // 传递过来的步数数据 - List wxSteps = steps.stream().map(v -> { - FitnessWalkStep step = new FitnessWalkStep(); - step.setActivityId(activityId); - step.setUserId(userId); - step.setStep(v.getInt("step")); - // 计算积分,判断有没有设置,超过达标步数后,每达标XX步数,获得XX积分 - int calculatedPoints = calculatePoints(isPoints, standSteps, standPoints, otherSteps, otherPoints, maxPoints, step.getStep()); - step.setPoints(calculatedPoints); - DateTime date = DateUtil.parse(DateUtil.format(DateUtil.date(v.getLong("timestamp") * 1000), "yyyy-MM-dd"), "yyyy-MM-dd"); - step.setApplyDate(date); - return step; - }).toList(); - - // 传递过来的所有日期的集合 - List dateList = wxSteps.stream().map(FitnessWalkStep::getApplyDate).collect(Collectors.toList()); - - // 去数据库查询日期,这个玩意还要保证插入到数据库的单个日期只有一条,并且这个日期数据库之前有步数,传过来没步数那就要保留数据库的步数 - // 上面日期集合数据库的步数 - List stepList = dao.query(FitnessWalkStep.class, Cnd.where("activityId", "=", activityId) - .and("userId", "=", userId).and("applyDate", "in", dateList)); - - // 这就是保留每天最大步数的集合,那这个集合里面applyDate就是唯一的了 - List dupStepList = new ArrayList<>(stepList.stream() - .collect(Collectors.toMap(FitnessWalkStep::getApplyDate, Function.identity(), - BinaryOperator.maxBy(Comparator.comparing(FitnessWalkStep::getStep)))) - .values()); - - // 这是过滤的相同的applyDate的集合,这些数据要删除 - List delStepIds = stepList.stream() - .filter(step -> !dupStepList.contains(step)) - .map(FitnessWalkStep::getId) - .collect(Collectors.toList()); - - // 数据库的数据 这个map就用于判断数据库的日期和传递过来日期的步数,两个是不是相等的,保留步数多的数据 - Map dateStepMap = dupStepList.stream().collect(Collectors.toMap(FitnessWalkStep::getApplyDate, v -> v)); - - List resultStepList = new ArrayList<>(); - // 这里循环就是,循环的传递过来的数据 - for (FitnessWalkStep step : wxSteps) { - // 数据库存储的步数 - FitnessWalkStep walkStep = dateStepMap.get(step.getApplyDate()); - // 如果数据库没有,那就要新增 - if (Lang.isEmpty(walkStep)) { - resultStepList.add(step); - } else { - // 如果数据库有,那就要保留步数多的 - if (walkStep.getStep() < step.getStep()) { - walkStep.setStep(step.getStep()); - resultStepList.add(walkStep); - } - } - } - - // 最后删除重复日期,更新步数数据 - if (Lang.isNotEmpty(delStepIds)) { - dao.clear(FitnessWalkStep.class, Cnd.where("id", "in", delStepIds)); - if (standSteps != null && standSteps > 0) { - // 删除积分明细 - dao.clear(IntegralDetail.class, Cnd.where("id", "in", delStepIds)); - } - } - if (Lang.isNotEmpty(resultStepList)) { - dao.insertOrUpdate(resultStepList); - if (standSteps != null && standSteps > 0) { - // 增加积分明细 - int year = DateUtil.thisYear(); - List integralDetailList = resultStepList.stream().map(v -> { - IntegralDetail detail = new IntegralDetail(); - detail.setId(v.getId()); - detail.setYear(String.valueOf(year)); - detail.setUserId(userId); - detail.setIntegral(String.valueOf(v.getPoints())); - detail.setIntegralNotes(integralNotes); - detail.setBizId(activityId); - detail.setIntegralTime(v.getApplyDate()); - return detail; - }).toList(); - - List list = integralDetailList.stream().filter(v -> !"0".equals(v.getIntegral())).toList(); - if (Lang.isNotEmpty(list)) { - dao.insertOrUpdate(list); - } - } - } - return Result.success(); - } catch (Exception e){ - e.printStackTrace(); - return Result.error("步数上传失败,请稍后重试,必要时请及时联系管理员"); - } + return fitnessWalkStepManageService.updateStepMonth(activityId, userId, monthSteps); } - /** - * 小程序获取开始时间至今的步数 + * 查询活动日期步数,返回step和毫秒级timestamp。 * - * @param activityId - * @param userId - * @return + * @param activityId 健步走云端活动ID + * @param userId 登录接口返回的用户ID + * @return Result,code为0表示成功,data承载查询活动日期步数,返回step和毫秒级timestamp结果,失败说明见msg */ @At @Ok("json:full") @ApiOperation("小程序获取开始时间至今的步数") public Result getActivityDateStep(String activityId, String userId) { - try { - Cnd cnd = Cnd.NEW(); - - FitnessWalkActivity activity = fitnessWalkCommonService.getActivity(activityId); - DateTime startTime = DateUtil.date(activity.getStartTime()); - DateTime endTime = DateUtil.date(activity.getEndTime()); - - cnd.and("activityId", "=", activityId); - cnd.and("userId", "=", userId); - cnd.and("applyDate", ">=", startTime); - cnd.and("applyDate", "<=", endTime); - cnd.asc("applyDate"); - cnd.groupBy("applyDate"); - List stepList = baseService.dao().query(FitnessWalkStep.class, cnd); - - List> collect = stepList.stream().map(v -> { - return Map.of("step", v.getStep(), "timestamp", v.getApplyDate().getTime()); - }).collect(Collectors.toList()); - - return Result.success().addData(collect); - - } catch (Exception e) { - return Result.error(e.getMessage()); - } + return fitnessWalkStepManageService.getActivityDateStep(activityId, userId); } + /** + * 按每日或自定义规则计算用户达标步数与完成进度。 + * + * @param activityId 健步走云端活动ID + * @param userId 登录接口返回的用户ID + * @return Result,code为0表示成功,data承载按每日或自定义规则计算用户达标步数与完成进度结果,失败说明见msg + */ @At @Ok("json:full") @ApiOperation("获取活动达标的进度条") public Result getActivityQualifyProgressBar(String activityId, String userId) { - NutMap resultMap = NutMap.NEW(); - - FitnessWalkActivity activity = fitnessWalkCommonService.getActivity(activityId); - //计步的抽奖模式 everyday custom - String lotteryMode = activity.getLotteryMode(); - if ("everyday".equals(lotteryMode)) { - FitnessWalkActivity.DayLotteryRule dayLotteryRule = activity.getDayLotteryRule(); - //每日抽奖资格步数 - Integer lotteryQualificationStep = dayLotteryRule.getLotteryQualificationStep(); - - FitnessWalkStep userWalkStep = baseService.dao().fetch(FitnessWalkStep.class, Cnd.where("userId", "=", userId) - .and("DATE( applyDate )", "=", DateUtil.today()).and("activityId", "=", activityId)); - - int complianceDay = baseService.dao().count(FitnessWalkStep.class, Cnd.where("userId", "=", userId) - .and("DATE( applyDate )", "=", DateUtil.today()).and("activityId", "=", activityId) - .and("step", ">", lotteryQualificationStep)); - - // 今天的步数 - Integer thisDayStep = userWalkStep.getStep(); - - // 去计算达标的百分比 - BigDecimal lotteryQualificationStepBig = new BigDecimal(lotteryQualificationStep); - BigDecimal thisDayStepBig = new BigDecimal(thisDayStep); - //达标的小数,保留四位 - BigDecimal divide = thisDayStepBig.divide(lotteryQualificationStepBig, 4, RoundingMode.HALF_UP); - BigDecimal percentValue = divide.multiply(new BigDecimal("100")); - // 格式化为两位小数 - DecimalFormat decimalFormat = new DecimalFormat("#.##"); - //百分数 - String formattedPercent = decimalFormat.format(percentValue); - - long betweenDay = DateUtil.date(activity.getStartTime()).between(DateUtil.date(activity.getEndTime()), DateUnit.DAY); - NutMap map = new NutMap(); - map.setv("mode", "everyDayLottery"); - //当前达标步数 - map.setv("userStep", thisDayStep); - //达标总步数 - map.setv("complianceSumStep", lotteryQualificationStep); - //达标比例 - map.setv("complianceProportion", Integer.parseInt(formattedPercent)); - //活动天数 - map.setv("betweenDay", betweenDay); - //达标天数 - map.setv("complianceDay", complianceDay); - resultMap.addv("everyDayLotteryMode", map); - } else if ("custom".equals(lotteryMode)) { - List customLotteryRules = activity.getCustomLotteryRules(); - List userWalkSteps = baseService.dao(). - query(FitnessWalkStep.class, Cnd.where("userId", "=", userId) - .and("activityId", "=", activityId) - .and("DATE(applyDate)", ">=", DateUtil.format(DateUtil.date(activity.getStartTime()), "yyyy-MM-dd")) - .and("DATE(applyDate)", "<=", DateUtil.format(DateUtil.date(activity.getEndTime()), "yyyy-MM-dd")) - ); - - List result = new ArrayList<>(); - - for (FitnessWalkActivity.CustomLotteryRule rule : customLotteryRules) { - NutMap nutMap = NutMap.NEW(); - - List partSteps = userWalkSteps.stream() - .filter(s -> (DateUtil.compare(s.getApplyDate(), rule.getStartDate()) >= 0 && DateUtil.compare(s.getApplyDate(), rule.getEndDate()) <= 0)) - .toList(); - - Integer lotteryQualificationDay = rule.getLotteryQualificationDay(); - Integer lotteryQualificationStep = rule.getLotteryQualificationStep(); - - if (lotteryQualificationDay != null && lotteryQualificationStep != null) { - //每天步数 - nutMap.put("mode", "customLotteryEveryDayStepMode"); - //用户总达标步数 - nutMap.put("userSumStep", partSteps.stream().filter(s -> DateUtil.compare(s.getApplyDate(), new Date(), "yyyy-MM-dd") == 0).findFirst().map(s -> s.getStep()).orElse(0)); - //今天达标步数 - nutMap.put("complianceStep", lotteryQualificationStep); - //总达标步数 - nutMap.put("complianceSumStep", lotteryQualificationDay * lotteryQualificationStep); - //用户当前达标总天数 - nutMap.put("userComplianceSumDay", partSteps.stream().filter(s -> s.getStep() > lotteryQualificationStep).count()); - //总达标天数 - nutMap.put("complianceSumDay", lotteryQualificationDay); - } else if (lotteryQualificationDay == null && lotteryQualificationStep != null) { - //总步数 - nutMap.put("mode", "customLotterySumStepMode"); - //用户总达标步数 - nutMap.put("userSumStep", partSteps.stream().mapToInt(FitnessWalkStep::getStep).sum()); - //总达标步数 - nutMap.put("complianceSumStep", lotteryQualificationStep); - - nutMap.put("title", DateUtil.format(rule.getStartDate(), "MM月dd日") + "至" + DateUtil.format(rule.getEndDate(), "MM月dd日")); - - } - result.add(nutMap); - } - resultMap.addv("customLotteryMode", result); - } - return Result.success().addData(resultMap); + return fitnessWalkStepManageService.getActivityQualifyProgressBar(activityId, userId); } /** - * 获取用户未读的奖 + * 查询指定用户在活动中的中奖记录。 * - * @param activityId - * @param userId - * @return + * @param activityId 健步走云端活动ID + * @param userId 登录接口返回的用户ID + * @return Result,code为0表示成功,data承载查询指定用户在活动中的中奖记录结果,失败说明见msg */ @At @Ok("json:full") @ApiOperation("获取用户未读的奖") public Result winningRecord(String activityId, String userId) { - Sql sql = Sqls.create(""" - select awardName,startDate,endDate from fitness_walk_award_user where activityId=@activityId and userId=@userId and isRead=@isRead - """).setParam("activityId",activityId).setParam("userId", userId).setParam("isRead",true); - - /* List awardUsers = baseService.dao().query(FitnessWalkAwardUser.class, - Cnd.where("activityId", "=", activityId) - .and("userId", "=", userId).and("isRead", "=", true));*/ - return Result.success(baseService.listMap(sql)); - } - - - - /** - * 计算积分 - * @param isPointsEnabled - * @param standSteps - * @param standPoints - * @param otherSteps - * @param otherPoints - * @param maxPoints - * @param actualSteps - * @return - */ - private int calculatePoints(boolean isPointsEnabled, - Integer standSteps, Integer standPoints, - Integer otherSteps, Integer otherPoints, - Integer maxPoints, int actualSteps) { - // 基础条件检查 - if (!isPointsEnabled || standSteps == null || standPoints == null || actualSteps < standSteps) { - return 0; - } - - // 计算基础积分 - int totalPoints = standPoints; - - // 计算额外积分 - if (hasExtraPoints(otherSteps, otherPoints)) { - int extraSteps = actualSteps - standSteps; - int extraPoints = (extraSteps / otherSteps) * otherPoints; - totalPoints += extraPoints; - } - - // 应用积分上限 - if (hasMaxPointsLimit(maxPoints)) { - return Math.min(totalPoints, maxPoints); - } - - return totalPoints; + return fitnessWalkStepManageService.winningRecord(activityId, userId); } /** - * 检查是否有额外积分规则 + * 标记活动奖项已读,沿用源版活动维度更新范围。 + * + * @param activityId 健步走云端活动ID + * @return Result,code为0表示成功,data承载标记活动奖项已读,沿用源版活动维度更新范围结果,失败说明见msg */ - private boolean hasExtraPoints(Integer otherSteps, Integer otherPoints) { - return otherSteps != null && otherPoints != null && otherSteps > 0 && otherPoints > 0; + @At + @Ok("json:full") + public Result hasReadAward(String activityId) { + return fitnessWalkStepManageService.hasReadAward(activityId); } /** * 检查是否有积分上限 */ - private boolean hasMaxPointsLimit(Integer maxPoints) { - return maxPoints != null && maxPoints > 0; - } + } diff --git a/src/main/java/com/budwk/app/zhgh/activity/fitnesswalk/controller/FitnessWalkStepRaffleController.java b/src/main/java/com/budwk/app/zhgh/activity/fitnesswalk/controller/FitnessWalkStepRaffleController.java index 726c742..1474278 100644 --- a/src/main/java/com/budwk/app/zhgh/activity/fitnesswalk/controller/FitnessWalkStepRaffleController.java +++ b/src/main/java/com/budwk/app/zhgh/activity/fitnesswalk/controller/FitnessWalkStepRaffleController.java @@ -1,45 +1,23 @@ package com.budwk.app.zhgh.activity.fitnesswalk.controller; -import cn.afterturn.easypoi.excel.ExcelExportUtil; -import cn.afterturn.easypoi.excel.entity.ExportParams; -import cn.afterturn.easypoi.excel.entity.enmus.ExcelType; -import cn.afterturn.easypoi.excel.entity.params.ExcelExportEntity; import cn.dev33.satoken.annotation.SaCheckPermission; -import cn.hutool.core.date.DateUtil; -import cn.hutool.core.util.StrUtil; import com.budwk.app.base.annotation.SLog; -import com.budwk.app.base.page.Pagination; import com.budwk.app.base.param.PageForm; import com.budwk.app.base.result.Result; -import com.budwk.app.base.service.BaseService; -import com.budwk.app.base.utils.CommonDownloadUtil; import com.budwk.app.zhgh.activity.fitnesswalk.contants.FitnessWalkMode; -import com.budwk.app.zhgh.activity.fitnesswalk.model.FitnessWalkAwardUser; import com.budwk.app.zhgh.activity.fitnesswalk.model.FitnessWalkRaffleRules; -import com.budwk.app.zhgh.activity.fitnesswalk.model.FitnessWalkRaffleUser; import com.budwk.app.zhgh.activity.fitnesswalk.service.FitnessWalkCommonService; import com.budwk.app.zhgh.activity.fitnesswalk.service.FitnessWalkStepRaffleService; -import com.budwk.app.zhgh.dayofficework.integral.model.IntegralDetail; import io.swagger.annotations.ApiOperation; -import org.apache.poi.ss.usermodel.Workbook; import org.nutz.aop.interceptor.ioc.TransAop; -import org.nutz.dao.Chain; -import org.nutz.dao.Cnd; import org.nutz.dao.Dao; -import org.nutz.dao.sql.Sql; -import org.nutz.dao.util.cri.SqlExpressionGroup; import org.nutz.ioc.aop.Aop; import org.nutz.ioc.loader.annotation.Inject; import org.nutz.ioc.loader.annotation.IocBean; -import org.nutz.lang.Lang; -import org.nutz.lang.util.NutMap; import org.nutz.mvc.annotation.At; import org.nutz.mvc.annotation.Ok; import org.nutz.mvc.annotation.Param; - import javax.servlet.http.HttpServletResponse; -import java.util.ArrayList; -import java.util.List; /** * @version 1.0 @@ -48,238 +26,166 @@ import java.util.List; * @Date 2024/12/27 14:43 * @注释 健步走抽奖控制器 */ -@IocBean @Ok("json") -@ApiOperation("健步走手动抽奖") +@IocBean @At("/platform/fitnessWalk/stepRaffle") public class FitnessWalkStepRaffleController { @Inject private Dao dao; - @Inject - private BaseService baseService; + @Inject private FitnessWalkCommonService fitnessWalkCommonService; @Inject private FitnessWalkStepRaffleService fitnessWalkStepRaffleService; - @At("") @Ok("beetl:/platform/zhgh/activity/fitnesswalk/stepRaffle.html") @SaCheckPermission("fitnessWalk.stepRaffle") public void index() { } - + /** + * 按活动达标条件分页检索具备抽奖资格的人员。 + * + * @param pageForm 分页、查询字段和排序参数,页码从1开始 + * @param activityId 健步走云端活动ID + * @param unionId 分工会ID,空值表示不追加筛选,已有数据权限仍生效 + * @param unitId 单位ID,空值表示不追加单位筛选 + * @return Result,code为0表示成功,data承载按活动达标条件分页检索具备抽奖资格的人员结果,失败说明见msg + */ @At - @ApiOperation("获取抽奖列表") @SaCheckPermission("fitnessWalk.stepRaffle") + @ApiOperation("获取抽奖列表") public Result pageData(PageForm pageForm, @Param("activityId") String activityId, @Param("unionId") String unionId, @Param("unitId") String unitId){ - Sql sql = fitnessWalkStepRaffleService.getRaffleSql(pageForm, activityId, unionId, unitId); - Pagination pagination = baseService.listPageMap(pageForm.getPageNumber(), pageForm.getPageSize(), sql); - return Result.success().addData(pagination); + return fitnessWalkStepRaffleService.pageData(pageForm, activityId, unionId, unitId); } - @At + @SaCheckPermission("fitnessWalk.stepRaffle") @ApiOperation("获取活动级联数据") - @SaCheckPermission("fitnessWalk.stepRaffle") public Result getCascadersActivity(@Param("year") int year) { - return Result.success().addData(fitnessWalkCommonService.cascaderActivity(year, FitnessWalkMode.STEP_COUNT)); + return Result.success(fitnessWalkCommonService.cascaderActivity(year, FitnessWalkMode.STEP_COUNT)); } - - @At - @Aop(TransAop.READ_COMMITTED) - @ApiOperation("设置抽奖规则,并抽奖") - @SLog(tag = "健步走手动抽奖", msg = "设置抽奖规则并抽奖") - @SaCheckPermission("fitnessWalk.stepRaffle") - public Result saveRaffle(FitnessWalkRaffleRules fitnessWalkRaffleRules){ - baseService.dao().insertOrUpdate(fitnessWalkRaffleRules); - fitnessWalkStepRaffleService.manualLottery(fitnessWalkRaffleRules.getActivityId()); - return null; - } - - - @At - @ApiOperation("获取抽奖规则") - @SaCheckPermission("fitnessWalk.stepRaffle") - public Result getRaffle(String activityId){ - FitnessWalkRaffleRules raffleRules = baseService.dao().fetch(FitnessWalkRaffleRules.class, Cnd.where("activityId", "=", activityId)); - return Result.success().addData(raffleRules); - } - - /** - * 小程序获取奖项信息 - * @param activityId - * @return + * 保存手动抽奖规则并按源版排名奖和达标奖规则执行抽奖。 + * + * @param fitnessWalkRaffleRules 活动ID及手动抽奖规则,奖项配置沿用源版本 + * @return Result,code为0表示成功,data承载保存手动抽奖规则并按源版排名奖和达标奖规则执行抽奖结果,失败说明见msg + */ + @At + @Aop(TransAop.READ_COMMITTED) + @SaCheckPermission("fitnessWalk.stepRaffle") + @ApiOperation("设置抽奖规则,并抽奖") + @SLog(tag = "健步走手动抽奖", msg = "设置抽奖规则并抽奖") + public Result saveRaffle(FitnessWalkRaffleRules fitnessWalkRaffleRules){ + return fitnessWalkStepRaffleService.saveRaffle(fitnessWalkRaffleRules); + } + + /** + * 读取活动已保存的手动抽奖规则。 + * + * @param activityId 健步走云端活动ID + * @return Result,code为0表示成功,data承载读取活动已保存的手动抽奖规则结果,失败说明见msg + */ + @At + @SaCheckPermission("fitnessWalk.stepRaffle") + @ApiOperation("获取抽奖规则") + public Result getRaffle(String activityId){ + return fitnessWalkStepRaffleService.getRaffle(activityId); + } + + /** + * 读取活动可用奖项及对应中奖配置。 + * + * @param activityId 健步走云端活动ID + * @return Result,code为0表示成功,data承载读取活动可用奖项及对应中奖配置结果,失败说明见msg */ @At @ApiOperation("小程序获取奖项信息") public Result getAwardList(String activityId){ - List activityAwardList = fitnessWalkStepRaffleService.getActivityAwardList(activityId); - return Result.success().addData(activityAwardList); + return fitnessWalkStepRaffleService.getAwardList(activityId); } - /** - * 小程序获取自己的中奖信息 - * @return + * 返回用户奖项名称和剩余抽奖次数。 + * + * @param activityId 健步走云端活动ID + * @param userId 登录接口返回的用户ID + * @return Result,code为0表示成功,data承载返回用户奖项名称和剩余抽奖次数结果,失败说明见msg */ @At @ApiOperation("小程序获取自己的中奖信息") public Result getAwardInfo(String activityId, String userId){ - Dao dao = fitnessWalkStepRaffleService.dao(); - FitnessWalkAwardUser awardUser = dao.fetch(FitnessWalkAwardUser.class, Cnd.where("activityId", "=", activityId).and("userId", "=", userId)); - FitnessWalkRaffleUser raffleUser = dao.fetch(FitnessWalkRaffleUser.class, Cnd.where("activityId", "=", activityId).and("userId", "=", userId)); - - NutMap map = NutMap.NEW(); - if (Lang.isNotEmpty(raffleUser) && !raffleUser.getIsRaffle()) { - if (Lang.isNotEmpty(awardUser)) { - map.put("awardName", awardUser.getAwardName()); - } else { - map.put("awardName", "没有中奖"); - } - map.put("raffleNum", 1); - } else { - map.put("raffleNum", 0); - } - return Result.success().addData(map); + return fitnessWalkStepRaffleService.getAwardInfo(activityId, userId); } - /** - * 小程序获取查询是否可以抽奖 - * @return + * 判断用户是否存在尚未使用的抽奖资格。 + * + * @param activityId 健步走云端活动ID + * @param userId 登录接口返回的用户ID + * @return Result,code为0表示成功,data承载判断用户是否存在尚未使用的抽奖资格结果,失败说明见msg */ @At @ApiOperation("小程序获取查询是否可以抽奖") public Result checkRaffle(String activityId, String userId){ - Dao dao = fitnessWalkStepRaffleService.dao(); - FitnessWalkRaffleUser raffleUser = dao.fetch(FitnessWalkRaffleUser.class, - Cnd.where("activityId", "=", activityId) - .and("userId", "=", userId)); - if (Lang.isNotEmpty(raffleUser)) { - return raffleUser.getIsRaffle() ? Result.error() : Result.success(); - } else { - return Result.error(); - } + return fitnessWalkStepRaffleService.checkRaffle(activityId, userId); } - /** - * 抽奖成功后,修改数据库状态 - * @param activityId - * @param userId - * @return + * 标记奖项和抽奖资格已读并登记奖项积分。 + * + * @param activityId 健步走云端活动ID + * @param userId 登录接口返回的用户ID + * @return Result,code为0表示成功,data承载标记奖项和抽奖资格已读并登记奖项积分结果,失败说明见msg */ @At @Aop(TransAop.READ_COMMITTED) @ApiOperation("抽奖成功后,修改数据库状态") @SLog(tag = "健步走手动抽奖", msg = "抽奖成功后,修改数据库状态") public Result updateUserAward(String activityId, String userId) { - fitnessWalkStepRaffleService.dao().update(FitnessWalkAwardUser.class, - Chain.make("isRead", true).add("applyDate", DateUtil.date()), - Cnd.where("activityId", "=", activityId).and("userId", "=", userId)); - - fitnessWalkStepRaffleService.dao().update(FitnessWalkRaffleUser.class, - Chain.make("isRaffle", true), - Cnd.where("activityId", "=", activityId).and("userId", "=", userId)); - // 增加积分 - FitnessWalkAwardUser awardUser = dao.fetch(FitnessWalkAwardUser.class, Cnd.where("activityId", "=", activityId).and("userId", "=", userId)); - IntegralDetail detail = new IntegralDetail(); - detail.setId(awardUser.getId()); - detail.setYear(String.valueOf(DateUtil.thisYear())); - detail.setUserId(awardUser.getUserId()); - detail.setIntegral(String.valueOf(awardUser.getPoints())); - detail.setIntegralNotes(awardUser.getAwardName()); - detail.setBizId(awardUser.getId()); - detail.setIntegralTime(DateUtil.date()); - dao.insert(detail); - return null; + return fitnessWalkStepRaffleService.updateUserAward(activityId, userId); } - + /** + * 按活动和奖项条件导出中奖人员。 + * + * @param searchKeyword 姓名、工号或当前查询字段的检索内容 + * @param activityId 健步走云端活动ID + * @param lotteryTime 中奖奖项筛选值,沿用页面提交格式 + * @param response HTTP响应,用于写入XSSF格式Excel文件流 + * 无返回对象,通过response写出Excel文件流。 + */ @At @Ok("void") - @ApiOperation("导出中奖名单") @SaCheckPermission("fitnessWalk.stepRaffle") + @ApiOperation("导出中奖名单") public void exportAwardExcel(String searchKeyword, String activityId, String lotteryTime, HttpServletResponse response) { - Cnd cnd = Cnd.NEW(); - cnd.andEX("activityId", "=", activityId); - cnd.andEX("awardName", "=", lotteryTime); - if (StrUtil.isNotBlank(searchKeyword)) { - SqlExpressionGroup seg = new SqlExpressionGroup(); - seg.orLike("username", searchKeyword); - seg.orLike("loginName", searchKeyword); - cnd.and(seg); - } - cnd.and("isRead", "=", true); - List list = fitnessWalkCommonService.dao().query(FitnessWalkAwardUser.class, cnd); - - for (FitnessWalkAwardUser awardUser : list) { - awardUser.setApplyDate(awardUser.getApplyDate()); - } - - List entityList = new ArrayList<>(); - ExcelExportEntity no = new ExcelExportEntity("序号", "no", 10); - no.setFormat("isAddIndex"); - entityList.add(no); - entityList.add(new ExcelExportEntity("工号", "loginName", 20)); - entityList.add(new ExcelExportEntity("姓名", "username", 20)); - entityList.add(new ExcelExportEntity("分工会", "unionName", 20)); - entityList.add(new ExcelExportEntity("单位", "unitName", 40)); - entityList.add(new ExcelExportEntity("奖项名称", "awardName", 20)); - entityList.add(new ExcelExportEntity("中奖时间", "applyDate2", 20)); - - try { - ExportParams exportParams = new ExportParams(); - exportParams.setType(ExcelType.XSSF); - Workbook workbook = ExcelExportUtil.exportExcel(exportParams, entityList, list); - workbook.write(response.getOutputStream()); - CommonDownloadUtil.download("健步走中奖人员名单.xlsx", workbook, response); - } catch (Exception e) { - e.printStackTrace(); - } + fitnessWalkStepRaffleService.exportAwardExcel(searchKeyword, activityId, lotteryTime, response); } - + /** + * 导出符合活动达标条件的抽奖候选人员。 + * + * @param searchKeyword 姓名、工号或当前查询字段的检索内容 + * @param activityId 健步走云端活动ID + * @param unionId 分工会ID,空值表示不追加筛选,已有数据权限仍生效 + * @param unitId 单位ID,空值表示不追加单位筛选 + * @param response HTTP响应,用于写入XSSF格式Excel文件流 + * 无返回对象,通过response写出Excel文件流。 + */ @At @Ok("void") - @ApiOperation("导出健步走达标人员名单") @SaCheckPermission("fitnessWalk.stepRaffle") + @ApiOperation("导出健步走达标人员名单") public void exportExcel(String searchKeyword, String activityId, String unionId, String unitId, HttpServletResponse response) { - PageForm pageForm = new PageForm(); - pageForm.setSearchKeyword(searchKeyword); - Sql sql = fitnessWalkStepRaffleService.getRaffleSql(pageForm, activityId, unionId, unitId); - - List list = fitnessWalkStepRaffleService.listMap(sql); - - List entityList = new ArrayList<>(); - ExcelExportEntity no = new ExcelExportEntity("序号", "no", 10); - no.setFormat("isAddIndex"); - entityList.add(no); - entityList.add(new ExcelExportEntity("工号", "loginname", 20)); - entityList.add(new ExcelExportEntity("姓名", "username", 20)); - entityList.add(new ExcelExportEntity("分工会", "unionname", 20)); - entityList.add(new ExcelExportEntity("单位", "unitname", 40)); - entityList.add(new ExcelExportEntity("达标天数", "standardsDays", 20)); - entityList.add(new ExcelExportEntity("步数", "total_steps", 20)); - - try { - ExportParams exportParams = new ExportParams(); - exportParams.setType(ExcelType.XSSF); - Workbook workbook = ExcelExportUtil.exportExcel(exportParams, entityList, list); - workbook.write(response.getOutputStream()); - CommonDownloadUtil.download("健步走达标人员名单.xlsx", workbook, response); - } catch (Exception e) { - e.printStackTrace(); - } + fitnessWalkStepRaffleService.exportExcel(searchKeyword, activityId, unionId, unitId, response); } } diff --git a/src/main/java/com/budwk/app/zhgh/activity/fitnesswalk/controller/FitnessWalkStepRankingController.java b/src/main/java/com/budwk/app/zhgh/activity/fitnesswalk/controller/FitnessWalkStepRankingController.java index 4afc3fe..384e526 100644 --- a/src/main/java/com/budwk/app/zhgh/activity/fitnesswalk/controller/FitnessWalkStepRankingController.java +++ b/src/main/java/com/budwk/app/zhgh/activity/fitnesswalk/controller/FitnessWalkStepRankingController.java @@ -1,51 +1,32 @@ package com.budwk.app.zhgh.activity.fitnesswalk.controller; -import cn.afterturn.easypoi.excel.ExcelExportUtil; -import cn.afterturn.easypoi.excel.entity.ExportParams; -import cn.afterturn.easypoi.excel.entity.enmus.ExcelType; -import cn.afterturn.easypoi.excel.entity.params.ExcelExportEntity; import cn.dev33.satoken.annotation.SaCheckPermission; -import cn.hutool.core.date.DateTime; -import cn.hutool.core.date.DateUtil; import com.budwk.app.base.annotation.SLog; -import com.budwk.app.base.page.Pagination; import com.budwk.app.base.param.PageForm; import com.budwk.app.base.result.Result; -import com.budwk.app.base.service.BaseService; -import com.budwk.app.base.utils.CommonDownloadUtil; import com.budwk.app.zhgh.activity.fitnesswalk.contants.FitnessWalkMode; -import com.budwk.app.zhgh.activity.fitnesswalk.model.FitnessWalkActivity; import com.budwk.app.zhgh.activity.fitnesswalk.service.FitnessWalkCommonService; import io.swagger.annotations.ApiOperation; import lombok.extern.slf4j.Slf4j; -import org.apache.poi.ss.usermodel.Workbook; -import org.nutz.dao.Sqls; -import org.nutz.dao.sql.Sql; import org.nutz.ioc.loader.annotation.Inject; import org.nutz.ioc.loader.annotation.IocBean; -import org.nutz.lang.util.NutMap; +import org.nutz.lang.Strings; import org.nutz.mvc.annotation.At; import org.nutz.mvc.annotation.Ok; import org.nutz.mvc.annotation.Param; - -import javax.servlet.http.HttpServletResponse; -import java.util.ArrayList; -import java.util.Date; import java.util.List; +import javax.servlet.http.HttpServletResponse; /** * 步数排行榜 */ -@Slf4j + @IocBean +@Slf4j @Ok("json") -@ApiOperation("健步走步数排行榜") @At("/platform/fitnessWalk/stepRanking") public class FitnessWalkStepRankingController { - @Inject - private BaseService baseService; - @Inject private FitnessWalkCommonService fitnessWalkCommonService; @@ -55,220 +36,178 @@ public class FitnessWalkStepRankingController { public void index() { } + /** + * 查询PC端步数排行榜。 + * + * @param pageForm 分页及姓名、工号查询参数,页码从1开始 + * @param activityId 活动ID,用于限定排行榜所属活动 + * @param unionId 分工会ID,空字符串表示全部分工会 + * @param unitId 单位ID,空字符串表示全部单位 + * @param mode 排行范围:today、oneMonth、halfYear、oneYear、activity;all 兼容原活动期间参数 + * @param sex 性别:空字符串表示全部,男或女表示指定性别 + * @param ageRange 年龄段:空字符串表示全体,under40、41to50、51to55、over55 表示指定年龄段;over55 表示年龄大于55岁 + * @return Result,data.list 为排行榜数据,data.totalCount 为符合条件的总人数 + */ @At - @ApiOperation("获取步数排行榜") @SaCheckPermission("fitnessWalk.stepRanking") + @ApiOperation("获取步数排行榜") public Result pageData(PageForm pageForm, @Param("activityId") String activityId, @Param("unionId") String unionId, @Param("unitId") String unitId, - @Param("mode") String mode) { - Pagination pagination = fitnessWalkCommonService.getStepRankingPagination(pageForm, activityId, unionId, unitId, mode); - return Result.success().addData(pagination); + @Param("mode") String mode, + @Param("sex") String sex, + @Param("ageRange") String ageRange) { + if (Strings.isNotBlank(sex) && !List.of("男", "女").contains(sex)) { + return Result.error("性别筛选参数不正确"); + } + if (Strings.isNotBlank(ageRange) && !List.of("under40", "41to50", "51to55", "over55").contains(ageRange)) { + return Result.error("年龄段筛选参数不正确"); + } + String actualMode = Strings.isBlank(mode) ? "today" : mode; + if (!List.of("today", "oneMonth", "halfYear", "oneYear", "activity", "all").contains(actualMode)) { + return Result.error("时间范围参数不正确"); + } + return Result.success(fitnessWalkCommonService.getStepRankingPagination( + pageForm, + activityId, + unionId, + unitId, + actualMode, + sex, + ageRange + )); } - /** - * 获取所有教工 今天的步数及活动时间范围内的步数 + * 查询小程序筛选排行榜。 * - * @param activityId - * @param mode day:今天 all:所有 - * @return {@link Object} + * @param activityId 活动ID,用于限定健步走活动数据 + * @param userId 当前登录用户ID,用于返回“我的指标”和“我的名次” + * @param rankingType 排行指标:totalSteps 累计步数、completionRate 目标完成率、complianceDays 达标天数 + * @param sex 性别筛选:空字符串表示全部,男或女表示指定性别 + * @param ageRange 年龄段:空字符串表示全体,under40、41to50、51to55、over55 表示指定年龄段;over55 表示年龄大于55岁 + * @param timeRange 时间范围:today 当日、oneMonth 近1个月、halfYear 近半年、oneYear 近1年、activity 活动开始至今 + * @param pageNumber 页码,从1开始 + * @param pageSize 每页条数,最大50条 + * @return Result,data 包含 list 排名列表、分页信息以及 currentUser 当前用户指标 */ @At @Ok("json:full") + @SLog(type = "校工会特色活动", tag = "健身走-计步步数排行榜", msg = "小程序接口(获取活动范围内步数排行榜)", param = true, result = true) @ApiOperation("获取今日步数及活动范围内步数") - @SLog(type = "校工会特色活动", tag = "健身走-计步步数排行榜", msg = "小程序接口(获取今日步数及活动范围内步数)") - public Result getUserStepRanking(String activityId, String mode, Integer pageNumber, Integer pageSize) { - if ("day".equals(mode)) { - Sql sql = Sqls.create(""" - SELECT - t.step, - t.applyDate, - u.username - FROM - `fitness_walk_step` t - LEFT JOIN sys_user u ON u.id = t.userId - WHERE - t.activityId = @activityId - AND t.applyDate = @today - GROUP BY u.id - ORDER BY t.step DESC - """); - sql.setParam("activityId", activityId); - sql.setParam("today", DateUtil.today()); - Pagination pagination = baseService.listPageMap(pageNumber, 50, sql); - return Result.success().addData(pagination); - } else if ("week".equals(mode)) { - // 周榜 - Date startOfWeek = DateUtil.beginOfWeek(new Date()); - Date endOfWeek = DateUtil.endOfWeek(new Date()); + public Result getUserStepRanking(String activityId, + String userId, + String rankingType, + String sex, + String ageRange, + String timeRange, + Integer pageNumber, + Integer pageSize) { + if (Strings.isBlank(activityId) || Strings.isBlank(userId)) { + return Result.error("活动ID和用户ID不能为空"); + } - Sql sql = Sqls.create(""" - SELECT - t.userId, - sum( t.step ) AS step, - u.username - FROM - `fitness_walk_step` t - LEFT JOIN sys_user u ON u.id = t.userId - WHERE - t.activityId = @activityId - AND t.applyDate >= @startTime - AND t.applyDate <= @endTime - GROUP BY - t.userId - ORDER BY - step DESC - """); - sql.setParam("activityId", activityId); - sql.setParam("startTime", startOfWeek); - sql.setParam("endTime", endOfWeek); - Pagination pagination = baseService.listPageMap(pageNumber, 50, sql); - return Result.success().addData(pagination); - } else if ("month".equals(mode)) { - // 月榜 - Date firstDayOfMonth = DateUtil.beginOfMonth(new Date()); - Date lastDayOfMonth = DateUtil.endOfMonth(new Date()); - Sql sql = Sqls.create(""" - SELECT - t.userId, - sum( t.step ) AS step, - u.username - FROM - `fitness_walk_step` t - LEFT JOIN sys_user u ON u.id = t.userId - WHERE - t.activityId = @activityId - AND t.applyDate >= @startTime - AND t.applyDate <= @endTime - GROUP BY - t.userId - ORDER BY - step DESC - """); - sql.setParam("activityId", activityId); - sql.setParam("startTime", firstDayOfMonth); - sql.setParam("endTime", lastDayOfMonth); - Pagination pagination = baseService.listPageMap(pageNumber, 50, sql); - return Result.success().addData(pagination); - } else if ("all".equals(mode)) { - // 总达标榜 - FitnessWalkActivity activity = fitnessWalkCommonService.getActivity(activityId); - DateTime startTime = DateUtil.date(activity.getStartTime()); - DateTime endTime = DateUtil.date(activity.getEndTime()); - Sql sql = Sqls.create(""" - SELECT - SUM(sub.max_step) AS step, - u.username - FROM ( - SELECT - userId, - activityId, - applyDate, - MAX(step) AS max_step - FROM - fitness_walk_step - WHERE - activityId = @activityId - AND applyDate >= @startTime AND applyDate <= @endTime - GROUP BY - userId, - activityId, - applyDate - ) sub - left join `user` u ON u.id = sub.userId - GROUP BY - userId - ORDER BY - step desc - """); - sql.setParam("activityId", activityId); - sql.setParam("startTime", startTime); - sql.setParam("endTime", endTime); - Pagination pagination = baseService.listPageMap(pageNumber, 50, sql); - return Result.success().addData(pagination); - } else if ("points".equals(mode)) { - // 积分榜 - FitnessWalkActivity activity = fitnessWalkCommonService.getActivity(activityId); - DateTime startTime = DateUtil.date(activity.getStartTime()); - DateTime endTime = DateUtil.date(activity.getEndTime()); - Sql sql = Sqls.create(""" - SELECT - SUM(sub.max_step) AS step, - u.username - FROM ( - SELECT - userId, - activityId, - applyDate, - MAX(points) AS max_step - FROM - fitness_walk_step - WHERE - activityId = @activityId - AND applyDate >= @startTime AND applyDate <= @endTime - GROUP BY - userId, - activityId, - applyDate - ) sub - left join `user` u ON u.id = sub.userId - GROUP BY - userId - ORDER BY - step desc - """); - sql.setParam("activityId", activityId); - sql.setParam("startTime", startTime); - sql.setParam("endTime", endTime); - Pagination pagination = baseService.listPageMap(pageNumber, 50, sql); - return Result.success().addData(pagination); - } else { - return null; + String actualRankingType = Strings.isBlank(rankingType) ? "totalSteps" : rankingType; + if (!List.of("totalSteps", "completionRate", "complianceDays").contains(actualRankingType)) { + return Result.error("不支持的排行榜指标"); + } + if (Strings.isNotBlank(sex) && !List.of("男", "女").contains(sex)) { + return Result.error("性别筛选参数不正确"); + } + if (Strings.isNotBlank(ageRange) && !List.of("under40", "41to50", "51to55", "over55").contains(ageRange)) { + return Result.error("年龄段筛选参数不正确"); + } + String actualTimeRange = Strings.isBlank(timeRange) ? "today" : timeRange; + if (!List.of("today", "oneMonth", "halfYear", "oneYear", "activity").contains(actualTimeRange)) { + return Result.error("时间范围参数不正确"); + } + + int actualPageNumber = pageNumber == null || pageNumber < 1 ? 1 : pageNumber; + int actualPageSize = pageSize == null || pageSize < 1 ? 20 : Math.min(pageSize, 50); + try { + return Result.success(fitnessWalkCommonService.getUserStepRanking( + activityId, + userId, + actualRankingType, + sex, + ageRange, + actualTimeRange, + actualPageNumber, + actualPageSize + )); + } catch (IllegalArgumentException e) { + return Result.error(e.getMessage()); } } - + /** + * 导出活动有效期内的参与人员累计步数。 + * + * @param searchName 查询字段,使用页面提供的字段名 + * @param searchKeyword 姓名、工号或当前查询字段的检索内容 + * @param activityId 健步走云端活动ID + * @param unionId 分工会ID,空值表示不追加筛选,已有数据权限仍生效 + * @param unitId 单位ID,空值表示不追加单位筛选 + * @param response HTTP响应,用于写入XSSF格式Excel文件流 + * 无返回对象,通过response写出Excel文件流。 + */ @At @Ok("void") - @ApiOperation("步数排行榜导出") - @SaCheckPermission("fitnessWalk.stepRanking") @SLog(type = "校工会特色活动", tag = "健身走-计步步数排行榜", msg = "步数排行榜导出", param = true, result = true) + @SaCheckPermission("fitnessWalk.stepRanking") + public void exportExcel2(@Param("searchName") String searchName, + @Param("searchKeyword") String searchKeyword, + @Param("activityId") String activityId, + @Param("unionId") String unionId, + @Param("unitId") String unitId, + HttpServletResponse response) { + fitnessWalkCommonService.exportTotalSteps(searchName, searchKeyword, activityId, unionId, unitId, response); + } + + /** + * 按时间、性别和年龄筛选导出当前步数排行榜。 + * + * @param searchName 查询字段,使用页面提供的字段名 + * @param searchKeyword 姓名、工号或当前查询字段的检索内容 + * @param activityId 健步走云端活动ID + * @param unionId 分工会ID,空值表示不追加筛选,已有数据权限仍生效 + * @param unitId 单位ID,空值表示不追加单位筛选 + * @param mode 时间范围:today、oneMonth、halfYear、oneYear或activity + * @param sex 性别筛选,男、女或空字符串 + * @param ageRange 年龄段筛选,under40、41to50、51to55、over55或空字符串 + * @param response HTTP响应,用于写入XSSF格式Excel文件流 + * 无返回对象,通过response写出Excel文件流。 + */ + @At + @Ok("void") + @SLog(type = "校工会特色活动", tag = "健身走-计步步数排行榜", msg = "步数排行榜导出", param = true, result = true) + @SaCheckPermission("fitnessWalk.stepRanking") + @ApiOperation("步数排行榜导出") public void exportExcel(@Param("searchName") String searchName, @Param("searchKeyword") String searchKeyword, @Param("activityId") String activityId, @Param("unionId") String unionId, @Param("unitId") String unitId, @Param("mode") String mode, + @Param("sex") String sex, + @Param("ageRange") String ageRange, HttpServletResponse response) { - List stepRankingList = fitnessWalkCommonService.getStepRankingList(searchName, searchKeyword, activityId, unionId, unitId, mode); - - List entityList = new ArrayList<>(); - ExcelExportEntity no = new ExcelExportEntity("序号", "no", 10); - no.setFormat("isAddIndex"); - entityList.add(no); - entityList.add(new ExcelExportEntity("姓名", "username", 20)); - entityList.add(new ExcelExportEntity("工号", "loginname", 20)); - entityList.add(new ExcelExportEntity("分工会", "unionname", 20)); - entityList.add(new ExcelExportEntity("单位", "unitname", 20)); - entityList.add(new ExcelExportEntity("today".equals(mode) ? "今日步数" : "活动期间总步数", "total_steps", 20)); - try { - String title = ("today".equals(mode) ? DateUtil.format(new Date(), "yyyy年MM月dd日") : "活动期间总") + "步数榜.xlsx"; - ExportParams exportParams = new ExportParams(); - exportParams.setType(ExcelType.XSSF); - Workbook workbook = ExcelExportUtil.exportExcel(exportParams, entityList, stepRankingList); - workbook.write(response.getOutputStream()); - CommonDownloadUtil.download(title, workbook, response); - } catch (Exception e) { - e.printStackTrace(); - } + fitnessWalkCommonService.exportStepRanking(searchName, searchKeyword, activityId, unionId, unitId, mode, sex, ageRange, response); } + /** + * 将排行榜时间范围参数转换为导出文件名中的中文说明。 + * + * @param mode 时间范围参数,支持 today、oneMonth、halfYear、oneYear、activity 和兼容值 all + * @return 导出文件名使用的时间范围名称 + * @param year 四位查询年度 + */ + @At - @ApiOperation("获取联级活动") @SaCheckPermission("fitnessWalk.stepRanking") + @ApiOperation("获取联级活动") public Result getCascadersActivity(@Param("year") int year) { - return Result.success().addData(fitnessWalkCommonService.cascaderActivity(year, FitnessWalkMode.STEP_COUNT)); + return Result.success(fitnessWalkCommonService.cascaderActivity(year, FitnessWalkMode.STEP_COUNT)); } } diff --git a/src/main/java/com/budwk/app/zhgh/activity/fitnesswalk/controller/FitnessWalkStepStatisticsController.java b/src/main/java/com/budwk/app/zhgh/activity/fitnesswalk/controller/FitnessWalkStepStatisticsController.java index 0763ad0..c0825b4 100644 --- a/src/main/java/com/budwk/app/zhgh/activity/fitnesswalk/controller/FitnessWalkStepStatisticsController.java +++ b/src/main/java/com/budwk/app/zhgh/activity/fitnesswalk/controller/FitnessWalkStepStatisticsController.java @@ -1,51 +1,28 @@ package com.budwk.app.zhgh.activity.fitnesswalk.controller; - -import cn.afterturn.easypoi.excel.ExcelExportUtil; -import cn.afterturn.easypoi.excel.entity.ExportParams; -import cn.afterturn.easypoi.excel.entity.enmus.ExcelType; -import cn.afterturn.easypoi.excel.entity.params.ExcelExportEntity; import cn.dev33.satoken.annotation.SaCheckPermission; -import cn.hutool.core.util.StrUtil; -import com.budwk.app.base.page.Pagination; import com.budwk.app.base.param.PageForm; import com.budwk.app.base.result.Result; -import com.budwk.app.base.service.BaseService; -import com.budwk.app.base.utils.CommonDownloadUtil; import com.budwk.app.zhgh.activity.fitnesswalk.contants.FitnessWalkMode; import com.budwk.app.zhgh.activity.fitnesswalk.service.FitnessWalkCommonService; -import com.budwk.app.zhgh.activity.fitnesswalk.utils.WeAppCloudUtil; +import com.budwk.app.zhgh.activity.fitnesswalk.service.FitnessWalkStepStatisticsService; import io.swagger.annotations.ApiOperation; -import org.apache.poi.ss.usermodel.Workbook; -import org.nutz.dao.Cnd; -import org.nutz.dao.Sqls; -import org.nutz.dao.sql.Sql; -import org.nutz.dao.util.cri.SqlExpressionGroup; -import org.nutz.integration.jedis.RedisService; import org.nutz.ioc.loader.annotation.Inject; import org.nutz.ioc.loader.annotation.IocBean; -import org.nutz.lang.Lang; -import org.nutz.lang.util.NutMap; import org.nutz.mvc.annotation.At; import org.nutz.mvc.annotation.Ok; import org.nutz.mvc.annotation.Param; - +import java.util.*; import javax.servlet.http.HttpServletResponse; -import java.util.ArrayList; -import java.util.List; @IocBean @Ok("json") -@ApiOperation("计步步数统计") @At("/platform/fitnessWalk/stepStatistics") public class FitnessWalkStepStatisticsController { @Inject - private BaseService baseService; - @Inject - private RedisService redisService; - @Inject - private WeAppCloudUtil weAppCloudUtil; + private FitnessWalkStepStatisticsService fitnessWalkStepStatisticsService; + @Inject private FitnessWalkCommonService fitnessWalkCommonService; @@ -56,110 +33,54 @@ public class FitnessWalkStepStatisticsController { } /** - * 公共sql - * @return + * 按活动、日期、工号及组织条件分页统计用户每日步数。 + * + * @param pageForm 分页、查询字段和排序参数,页码从1开始 + * @param year 查询年度,四位年份 + * @param activityId 健步走云端活动ID + * @param activityDate 起止日期数组,两项均为yyyy-MM-dd + * @param unionId 分工会ID,空值表示不追加筛选,已有数据权限仍生效 + * @param unitId 单位ID,空值表示不追加单位筛选 + * @return Result,code为0表示成功,data承载按活动、日期、工号及组织条件分页统计用户每日步数结果,失败说明见msg */ - private String getSql() { - return """ - SELECT - u.loginname, - u.username, - u.unionName AS unionname, - u.unitName AS unitname, - al.applyDate, - al.step, - date_format(al.applyDate,'%Y-%m-%d') as prizeData - FROM - `fitness_walk_step` al - LEFT JOIN `vw_user` u ON u.id = al.userId - $condition - """; - } - @At @Ok("json:full") - @ApiOperation("计步步数统计") @SaCheckPermission("fitnessWalk.stepStatistics") + @ApiOperation("计步步数统计") public Result pageData(PageForm pageForm, @Param("year") Integer year, @Param("activityId") String activityId, @Param("activityDate") String[] activityDate, @Param("unionId") String unionId, @Param("unitId") String unitId) { - Cnd cnd = Cnd.NEW(); - Sql sql = Sqls.create(getSql()); - if (Lang.isNotEmpty(activityDate)) { - cnd.and("date_format(al.applyDate,'%Y-%m-%d')", ">=", activityDate[0]); - cnd.and("date_format(al.applyDate,'%Y-%m-%d')", "<=", activityDate[1]); - } - if(StrUtil.isNotBlank(pageForm.getSearchKeyword())){ - SqlExpressionGroup seg = new SqlExpressionGroup(); - seg.orLike("u.username",pageForm.getSearchKeyword()); - seg.orLike("u.loginname",pageForm.getSearchKeyword()); - cnd.and(seg); - } - cnd.andEX("YEAR(al.applyDate)", "=", year); - cnd.andEX("al.activityId", "=", activityId); - cnd.andEX("u.unionid", "=", unionId); - cnd.andEX("u.unitId", "=", unitId); - cnd.desc("al.applyDate"); - cnd.desc("u.unioncode"); - cnd.groupBy("al.applyDate", "al.userId"); - - sql.setCondition(cnd); - Pagination pagination = baseService.listPageMap(pageForm.getPageNumber(), pageForm.getPageSize(), sql); - return Result.success().addData(pagination); + return fitnessWalkStepStatisticsService.pageData(pageForm, year, activityId, activityDate, unionId, unitId); } - @At - @ApiOperation("获取联级活动") @SaCheckPermission("fitnessWalk.stepStatistics") + @ApiOperation("获取联级活动") public Result getCascadersActivity(@Param("year") int year) { - return Result.success().addData(fitnessWalkCommonService.cascaderActivity(year, FitnessWalkMode.STEP_COUNT)); + return Result.success(fitnessWalkCommonService.cascaderActivity(year, FitnessWalkMode.STEP_COUNT)); } - - @Ok("void") + /** + * 按当前活动和日期条件导出计步明细。 + * + * @param activityId 健步走云端活动ID + * @param activityDate 起止日期数组,两项均为yyyy-MM-dd + * @param unionId 分工会ID,空值表示不追加筛选,已有数据权限仍生效 + * @param unitId 单位ID,空值表示不追加单位筛选 + * @param response HTTP响应,用于写入XSSF格式Excel文件流 + * 无返回对象,通过response写出Excel文件流。 + */ @At("/exportExcel") - @ApiOperation("计步步数统计表导出") + @Ok("void") @SaCheckPermission("fitnessWalk.stepStatistics") + @ApiOperation("计步步数统计表导出") public void exportExcel(@Param("activityId") String activityId, @Param("activityDate") String[] activityDate, @Param("unionId") String unionId, @Param("unitId") String unitId, HttpServletResponse response) { - Cnd cnd = Cnd.NEW(); - Sql sql = Sqls.create(getSql()); - if (activityDate.length > 0) { - cnd.and("date_format(al.applyDate,'%Y-%m-%d')", ">=", activityDate[0]); - cnd.and("date_format(al.applyDate,'%Y-%m-%d')", "<=", activityDate[1]); - } - cnd.andEX("al.activityId", "=", activityId); - cnd.andEX("u.unionid", "=", unionId); - cnd.andEX("u.unitId", "=", unitId); - cnd.desc("al.applyDate"); - cnd.desc("u.unioncode"); - sql.setCondition(cnd); - - List exportList = baseService.listMap(sql); - - List entityList = new ArrayList<>() {{ - add(new ExcelExportEntity("姓名", "username", 20)); - add(new ExcelExportEntity("工号", "loginname", 20)); - add(new ExcelExportEntity("单位", "unitname", 20)); - add(new ExcelExportEntity("分工会", "unionname", 20)); - add(new ExcelExportEntity("当前步数", "step", 20)); - add(new ExcelExportEntity("日期", "prizeData", 20)); - }}; - - try { - ExportParams exportParams = new ExportParams(); - exportParams.setType(ExcelType.XSSF); - Workbook workbook = ExcelExportUtil.exportExcel(exportParams, entityList, exportList); - workbook.write(response.getOutputStream()); - CommonDownloadUtil.download("计步步数统计表.xlsx", workbook, response); - } catch (Exception e) { - e.printStackTrace(); - } + fitnessWalkStepStatisticsService.exportExcel(activityId, activityDate, unionId, unitId, response); } } diff --git a/src/main/java/com/budwk/app/zhgh/activity/fitnesswalk/controller/FitnessWalkStepWiningController.java b/src/main/java/com/budwk/app/zhgh/activity/fitnesswalk/controller/FitnessWalkStepWiningController.java index 5ca384d..1fe1a38 100644 --- a/src/main/java/com/budwk/app/zhgh/activity/fitnesswalk/controller/FitnessWalkStepWiningController.java +++ b/src/main/java/com/budwk/app/zhgh/activity/fitnesswalk/controller/FitnessWalkStepWiningController.java @@ -1,126 +1,79 @@ package com.budwk.app.zhgh.activity.fitnesswalk.controller; -import cn.afterturn.easypoi.excel.ExcelExportUtil; -import cn.afterturn.easypoi.excel.entity.ExportParams; -import cn.afterturn.easypoi.excel.entity.params.ExcelExportEntity; import cn.dev33.satoken.annotation.SaCheckPermission; -import cn.hutool.core.date.DateUtil; -import cn.hutool.core.util.StrUtil; import com.budwk.app.base.annotation.SLog; import com.budwk.app.base.result.Result; -import com.budwk.app.base.service.BaseService; import com.budwk.app.zhgh.activity.fitnesswalk.contants.FitnessWalkMode; -import com.budwk.app.zhgh.activity.fitnesswalk.model.FitnessWalkAwardUser; import com.budwk.app.zhgh.activity.fitnesswalk.service.FitnessWalkCommonService; import io.swagger.annotations.ApiOperation; -import org.apache.poi.ss.usermodel.Workbook; -import org.nutz.dao.Cnd; -import org.nutz.dao.util.cri.SqlExpressionGroup; import org.nutz.ioc.loader.annotation.Inject; import org.nutz.ioc.loader.annotation.IocBean; import org.nutz.lang.util.NutMap; import org.nutz.mvc.annotation.At; import org.nutz.mvc.annotation.Ok; import org.nutz.mvc.annotation.Param; - -import javax.servlet.http.HttpServletResponse; -import java.io.BufferedOutputStream; -import java.net.URLEncoder; -import java.util.ArrayList; import java.util.List; -import java.util.zip.ZipEntry; -import java.util.zip.ZipOutputStream; +import javax.servlet.http.HttpServletResponse; /** * 用户中奖榜 */ -@IocBean @Ok("json") -@ApiOperation("小程序计步步数中奖榜") +@IocBean @At("/platform/fitnessWalk/stepWining") public class FitnessWalkStepWiningController { - @Inject - private BaseService baseService; - @Inject private FitnessWalkCommonService fitnessWalkCommonService; - @At("") @Ok("beetl:/platform/zhgh/activity/fitnesswalk/stepWining.html") @SaCheckPermission("fitnessWalk.stepWining") public void index() { } - /** * 获取中奖榜 * - * @param activityId + * @param activityId 健步走云端活动ID * @return {@link Object} */ @At @Ok("json:full") + @SLog(type = "校工会特色活动",tag = "健身走-计步步数中奖榜",msg = "获取中奖榜",param = true,result = true) @ApiOperation("小程序获取中奖榜") - @SLog(type = "校工会特色活动",tag = "健身走-计步步数中奖榜",msg = "获取中奖榜") public Result getUserStepWining(String activityId) { - return Result.success().addData(fitnessWalkCommonService.getWiningRankingUserList(activityId)); + return Result.success(fitnessWalkCommonService.getWiningRankingUserList(activityId)); } @At - @ApiOperation("获取联级活动") @SaCheckPermission("fitnessWalk.stepWining") + @ApiOperation("获取联级活动") public Result getCascadersActivity(@Param("year") int year) { - return Result.success().addData(fitnessWalkCommonService.cascaderActivity(year, FitnessWalkMode.STEP_COUNT)); + return Result.success(fitnessWalkCommonService.cascaderActivity(year, FitnessWalkMode.STEP_COUNT)); } - + /** + * 按活动抽奖规则导出各期中奖榜。 + * + * @param activityId 健步走云端活动ID + * @param response HTTP响应,用于写入XSSF格式Excel文件流 + * 无返回对象,通过response写出Excel文件流。 + */ @At @Ok("void") - @ApiOperation("小程序中奖榜导出") + @SLog(type = "校工会特色活动",tag = "健身走-计步步数中奖榜",msg = "导出中奖榜",param = true,result = true) @SaCheckPermission("fitnessWalk.stepWining") - @SLog(type = "校工会特色活动",tag = "健身走-计步步数中奖榜",msg = "导出中奖榜") + @ApiOperation("小程序中奖榜导出") public void exportUserStepWining(String activityId, HttpServletResponse response){ - List winingRankingUserList = fitnessWalkCommonService.getWiningRankingUserList(activityId); - List entityList = new ArrayList<>(); - ExcelExportEntity no = new ExcelExportEntity("序号", "no", 10); - no.setFormat("isAddIndex"); - entityList.add(no); - entityList.add(new ExcelExportEntity("工号", "loginName", 20)); - entityList.add(new ExcelExportEntity("姓名", "username", 20)); - entityList.add(new ExcelExportEntity("分工会", "unionName", 20)); - entityList.add(new ExcelExportEntity("单位", "unitName", 40)); - entityList.add(new ExcelExportEntity("奖项名称", "awardName", 20)); - entityList.add(new ExcelExportEntity("步数", "awardSteps", 20)); - entityList.add(new ExcelExportEntity("中奖时间", "applyDate", 20)); - try { - response.setContentType("application/octet-stream"); - response.setHeader("content-disposition", "attachment;filename=" - + URLEncoder.encode("中奖明细.zip", "UTF-8")); - ZipOutputStream zipOutputStream = new ZipOutputStream(new BufferedOutputStream(response.getOutputStream())); - for (NutMap map : winingRankingUserList) { - List awardList = map.getAsList("awardList", NutMap.class); - awardList.forEach(v->v.setv("applyDate",DateUtil.format(v.getTime("applyDate"),"yyyy-MM-dd"))); - zipOutputStream.putNextEntry(new ZipEntry(map.getString("title") + ".xlsx")); - Workbook workbook = ExcelExportUtil.exportExcel(new ExportParams(), entityList, awardList); - workbook.write(zipOutputStream); - zipOutputStream.closeEntry(); - } - zipOutputStream.flush(); - zipOutputStream.close(); - } catch (Exception e) { - e.printStackTrace(); - } - + fitnessWalkCommonService.exportUserStepWining(activityId, response); } - /** * 中奖奖项列表 * - * @param activityId - * @return + * @param activityId 健步走云端活动ID + * @return Result,code为0表示成功,data为业务结果,msg为操作说明 */ @At @ApiOperation("小程序获取中奖奖项列表") @@ -130,26 +83,17 @@ public class FitnessWalkStepWiningController { } /** - * 中奖用户 - * @param lotteryTime - * @param activityId - * @return + * 按活动、奖项和人员关键字检索中奖人员。 + * + * @param lotteryTime 中奖奖项筛选值,沿用页面提交格式 + * @param activityId 健步走云端活动ID + * @param searchKeyword 姓名、工号或当前查询字段的检索内容 + * @return Result,code为0表示成功,data承载按活动、奖项和人员关键字检索中奖人员结果,失败说明见msg */ @At @ApiOperation("小程序获取中奖用户") public Result prizeUsers(String lotteryTime, String activityId, String searchKeyword) { - Cnd cnd = Cnd.NEW(); - cnd.andEX("activityId", "=", activityId); - cnd.andEX("awardName", "=", lotteryTime); - if (StrUtil.isNotBlank(searchKeyword)) { - SqlExpressionGroup seg = new SqlExpressionGroup(); - seg.orLike("username", searchKeyword); - seg.orLike("loginName", searchKeyword); - cnd.and(seg); - } - cnd.and("isRead", "=", true); - List awardUserList = fitnessWalkCommonService.dao().query(FitnessWalkAwardUser.class, cnd); - return Result.success(awardUserList); + return fitnessWalkCommonService.prizeUsers(lotteryTime, activityId, searchKeyword); } } diff --git a/src/main/java/com/budwk/app/zhgh/activity/fitnesswalk/model/FitnessWalkActivity.java b/src/main/java/com/budwk/app/zhgh/activity/fitnesswalk/model/FitnessWalkActivity.java index 6b4823d..f50830f 100644 --- a/src/main/java/com/budwk/app/zhgh/activity/fitnesswalk/model/FitnessWalkActivity.java +++ b/src/main/java/com/budwk/app/zhgh/activity/fitnesswalk/model/FitnessWalkActivity.java @@ -1,11 +1,10 @@ package com.budwk.app.zhgh.activity.fitnesswalk.model; import io.swagger.annotations.ApiModelProperty; -import lombok.Data; - import java.io.Serializable; import java.util.Date; import java.util.List; +import lombok.Data; @Data public class FitnessWalkActivity { @@ -163,6 +162,36 @@ public class FitnessWalkActivity { @ApiModelProperty(value = "封顶积分") private Integer maxPoints; + // 关联活动ID,创建/编辑时通过下拉选择后回填 + private String relationActivityId; + + // 关联活动名称,兼容源版活动数据 + private String relationActivityName; + + // 关联福利项目ID,创建/编辑时通过福利下拉选择后回填 + private String welfareId; + + // 关联活动对应的达标活动数量,兼容源版活动数据 + private Integer relationStandardActivityNum; + + // 是否自动连续创建活动 + private Boolean autoContinuousCreate; + + // 连续创建活动日期区间,页面开启自动连续创建后由用户选择开始和结束日期 + private Long[] continuousCreateDate; + + // 自动创建来源活动ID,仅自动复制出的活动会写入,用于编辑原活动时识别已落地的历史活动 + private String autoCreateSourceActivityId; + + // 自动创建对应的目标活动日期(当天00:00:00时间戳),用于避免同一天活动被重复生成 + private Long autoCreateTargetDate; + + // 创建时间,用于筛选当年已创建的活动 + private Long createdAt; + + // 修改时间,保存编辑时同步更新 + private Long updatedAt; + @Data public static class Point implements Serializable { private String type; diff --git a/src/main/java/com/budwk/app/zhgh/activity/fitnesswalk/service/FitnessWalkCommonService.java b/src/main/java/com/budwk/app/zhgh/activity/fitnesswalk/service/FitnessWalkCommonService.java index 30e0341..a570f54 100644 --- a/src/main/java/com/budwk/app/zhgh/activity/fitnesswalk/service/FitnessWalkCommonService.java +++ b/src/main/java/com/budwk/app/zhgh/activity/fitnesswalk/service/FitnessWalkCommonService.java @@ -2,14 +2,17 @@ package com.budwk.app.zhgh.activity.fitnesswalk.service; import com.budwk.app.base.page.Pagination; import com.budwk.app.base.param.PageForm; +import com.budwk.app.base.result.Result; import com.budwk.app.base.service.BaseService; import com.budwk.app.zhgh.activity.fitnesswalk.contants.Cascader; import com.budwk.app.zhgh.activity.fitnesswalk.contants.FitnessWalkMode; import com.budwk.app.zhgh.activity.fitnesswalk.model.FitnessWalkActivity; import com.budwk.app.zhgh.activity.fitnesswalk.model.FitnessWalkAwardUser; -import org.nutz.lang.util.NutMap; - +import com.budwk.app.zhgh.activity.fitnesswalk.service.FitnessWalkCommonService; +import java.util.Date; import java.util.List; +import javax.servlet.http.HttpServletResponse; +import org.nutz.lang.util.NutMap; public interface FitnessWalkCommonService extends BaseService { @@ -24,7 +27,6 @@ public interface FitnessWalkCommonService extends BaseService cascaderActivity(int year, FitnessWalkMode mode); - /** * 添加活动或修改活动,存放至redis * @@ -45,7 +46,6 @@ public interface FitnessWalkCommonService extends BaseService getStepRankingList(String searchName, String searchKeyword, String activityId, String unionId, String unitId, String mode); + /** + * 查询PC端排行榜导出数据,查询条件及返回字段与页面表格保持一致。 + * + * @param searchName 姓名或工号对应的查询字段 + * @param searchKeyword 姓名或工号查询内容 + * @param activityId 活动ID + * @param unionId 分工会ID,空字符串表示全部 + * @param unitId 单位ID,空字符串表示全部 + * @param mode 排行范围:today、oneMonth、halfYear、oneYear、activity;all兼容活动期间 + * @param sex 性别:空字符串表示全部,男或女表示指定性别 + * @param ageRange 年龄段:空字符串表示全部,under40、41to50、51to55、over55表示指定年龄段;over55 表示年龄大于55岁 + * @return 排行榜导出数据,包含页面表格展示的用户资料、步数和达标天数 + */ + List getStepRankingList(String searchName, + String searchKeyword, + String activityId, + String unionId, + String unitId, + String mode, + String sex, + String ageRange); + /** + * 查询小程序筛选排行榜。 + * + * @param activityId 活动ID + * @param userId 当前登录用户ID + * @param rankingType 排行指标:totalSteps、completionRate、complianceDays + * @param sex 性别筛选,空字符串表示全部 + * @param ageRange 年龄段筛选,空字符串表示全体 + * @param timeRange 时间范围:today、oneMonth、halfYear、oneYear、activity + * @param pageNumber 页码,从1开始 + * @param pageSize 每页条数 + * @return 包含排名列表、分页信息以及当前用户指标的 NutMap + */ + NutMap getUserStepRanking(String activityId, + String userId, + String rankingType, + String sex, + String ageRange, + String timeRange, + Integer pageNumber, + Integer pageSize); /** * 获取中奖榜 @@ -83,4 +140,83 @@ public interface FitnessWalkCommonService extends BaseService prizeOption(String activityId); + /** + * 补全健步走用户的基础资料。 + * + * @param userId 用户ID,用于定位 sys_user 中的当前用户 + * @param sex 性别,当前接口只接受“男”或“女” + * @param birthday 出生日期,不能晚于当前日期 + * @return 包含 sex、birthday 的 NutMap;用户不存在时返回 null + */ + NutMap completeUserInfo(String userId, String sex, Date birthday); + + /** + * 按姓名和工号匹配小程序用户并返回单位及角色资料。 + * + * @param username 用户姓名,与工号共同匹配人员 + * @param loginname 用户工号 + * @return Result,code为0表示成功,data承载按姓名和工号匹配小程序用户并返回单位及角色资料结果,失败说明见msg + */ + Result login(String username, String loginname); + + /** + * 导出活动有效期内的参与人员累计步数。 + * + * @param searchName 查询字段,使用页面提供的字段名 + * @param searchKeyword 姓名、工号或当前查询字段的检索内容 + * @param activityId 健步走云端活动ID + * @param unionId 分工会ID,空值表示不追加筛选,已有数据权限仍生效 + * @param unitId 单位ID,空值表示不追加单位筛选 + * @param response HTTP响应,用于写入XSSF格式Excel文件流 + * 无返回对象,通过response写出Excel文件流。 + */ + void exportTotalSteps(String searchName, + String searchKeyword, + String activityId, + String unionId, + String unitId, + HttpServletResponse response); + + /** + * 按时间、性别和年龄筛选导出当前步数排行榜。 + * + * @param searchName 查询字段,使用页面提供的字段名 + * @param searchKeyword 姓名、工号或当前查询字段的检索内容 + * @param activityId 健步走云端活动ID + * @param unionId 分工会ID,空值表示不追加筛选,已有数据权限仍生效 + * @param unitId 单位ID,空值表示不追加单位筛选 + * @param mode 时间范围:today、oneMonth、halfYear、oneYear或activity + * @param sex 性别筛选,男、女或空字符串 + * @param ageRange 年龄段筛选,under40、41to50、51to55、over55或空字符串 + * @param response HTTP响应,用于写入XSSF格式Excel文件流 + * 无返回对象,通过response写出Excel文件流。 + */ + void exportStepRanking(String searchName, + String searchKeyword, + String activityId, + String unionId, + String unitId, + String mode, + String sex, + String ageRange, + HttpServletResponse response); + + /** + * 按活动抽奖规则导出各期中奖榜。 + * + * @param activityId 健步走云端活动ID + * @param response HTTP响应,用于写入XSSF格式Excel文件流 + * 无返回对象,通过response写出Excel文件流。 + */ + void exportUserStepWining(String activityId, HttpServletResponse response); + + /** + * 按活动、奖项和人员关键字检索中奖人员。 + * + * @param lotteryTime 中奖奖项筛选值,沿用页面提交格式 + * @param activityId 健步走云端活动ID + * @param searchKeyword 姓名、工号或当前查询字段的检索内容 + * @return Result,code为0表示成功,data承载按活动、奖项和人员关键字检索中奖人员结果,失败说明见msg + */ + Result prizeUsers(String lotteryTime, String activityId, String searchKeyword); } diff --git a/src/main/java/com/budwk/app/zhgh/activity/fitnesswalk/service/FitnessWalkPunchStatisticsService.java b/src/main/java/com/budwk/app/zhgh/activity/fitnesswalk/service/FitnessWalkPunchStatisticsService.java index 8e626fa..f97784c 100644 --- a/src/main/java/com/budwk/app/zhgh/activity/fitnesswalk/service/FitnessWalkPunchStatisticsService.java +++ b/src/main/java/com/budwk/app/zhgh/activity/fitnesswalk/service/FitnessWalkPunchStatisticsService.java @@ -1,20 +1,182 @@ package com.budwk.app.zhgh.activity.fitnesswalk.service; -import cn.hutool.json.JSONObject; +import com.alibaba.fastjson.JSONObject; +import com.budwk.app.base.page.Pagination; import com.budwk.app.base.service.BaseService; +import com.budwk.app.zhgh.activity.fitnesswalk.contants.Cascader; import com.budwk.app.zhgh.activity.fitnesswalk.model.FitnessWalkAwardUser; -import org.nutz.lang.util.NutMap; - +import com.budwk.app.zhgh.activity.fitnesswalk.service.FitnessWalkPunchStatisticsService; import java.util.List; +import javax.servlet.http.HttpServletResponse; +import org.nutz.lang.util.NutMap; public interface FitnessWalkPunchStatisticsService extends BaseService { - List unionRegCheckInNum(String activityId, String unionId); + /** + * 查询人员打卡统计页专用活动级联。 + * 父级使用稳定的关联项目ID,不复用公共活动级联,避免父级 value 随最新活动变化。 + * + * @param year 查询年份 + * @return 人员打卡统计页活动级联 + */ + List userPunchCascaderActivity(int year); - List unionRegistrationData(String activityId, String unionId); + /** + * 查询分工会报名人数、打卡人数。 + * 选择分活动时只查当前分活动;只选择主活动时按主活动关联项目下的全部活动汇总查询。 + * + * @param mainActivityId 主活动ID + * @param subActivityId 分活动ID + * @param activityId 兼容原页面保留的最终活动ID + * @param unionId 分工会ID + * @return 分工会统计结果 + */ + List unionRegCheckInNum(String mainActivityId, String subActivityId, String activityId, String unionId); - List unionCheckInData(String activityId, String unionId); + /** + * 查询某个分工会的报名明细。 + * 选择分活动时只查当前分活动;只选择主活动时按主活动关联项目下的全部活动汇总查询。 + * + * @param mainActivityId 主活动ID + * @param subActivityId 分活动ID + * @param activityId 兼容原页面保留的最终活动ID + * @param unionId 分工会ID + * @return 报名明细 + */ + List unionRegistrationData(String mainActivityId, String subActivityId, String activityId, String unionId); - List exportNotGetGiftVoucherUsers(String activityId); - List exportNotUseGiftVoucherUsers(String activityId); + /** + * 查询某个分工会的打卡明细。 + * 选择分活动时只查当前分活动;只选择主活动时按主活动关联项目下的全部活动汇总查询。 + * + * @param mainActivityId 主活动ID + * @param subActivityId 分活动ID + * @param activityId 兼容原页面保留的最终活动ID + * @param unionId 分工会ID + * @return 打卡明细 + */ + List unionCheckInData(String mainActivityId, String subActivityId, String activityId, String unionId); + + /** + * 导出未取得礼品券用户。 + * + * @param mainActivityId 主活动ID + * @param subActivityId 分活动ID + * @param activityId 兼容原页面保留的最终活动ID + * @return 未取得礼品券用户 + */ + List exportNotGetGiftVoucherUsers(String mainActivityId, String subActivityId, String activityId); + + /** + * 导出未使用礼品券用户。 + * + * @param mainActivityId 主活动ID + * @param subActivityId 分活动ID + * @param activityId 兼容原页面保留的最终活动ID + * @return 未使用礼品券用户 + */ + List exportNotUseGiftVoucherUsers(String mainActivityId, String subActivityId, String activityId); + + /** + * 查询人员打卡情况列表。 + * 选择分活动时只查当前分活动;只选择主活动时按主活动关联项目下的全部活动汇总到人员维度。 + * + * @param mainActivityId 主活动ID + * @param subActivityId 分活动ID + * @param activityId 兼容页面保留的最终活动ID + * @param unionId 分工会ID + * @param searchKeyword 姓名或工号关键字,空表示不过滤 + * @param qualifiedStatus 达标筛选:qualified=已达标,unqualified=未达标,all/空=全部 + * @param pageNumber 页码,从 1 开始 + * @param pageSize 每页条数 + * @return 人员打卡情况分页结果 + */ + Pagination userPunchPageData(String mainActivityId, String subActivityId, String activityId, String unionId, + String searchKeyword, String qualifiedStatus, int pageNumber, int pageSize); + + /** + * 导出人员打卡情况列表。 + * 导出会按当前筛选条件返回全部人员,不能只返回页面当前页。 + * + * @param mainActivityId 主活动ID + * @param subActivityId 分活动ID + * @param activityId 兼容页面保留的最终活动ID + * @param unionId 分工会ID + * @param searchKeyword 姓名或工号关键字,空表示不过滤 + * @param qualifiedStatus 达标筛选:qualified=已达标,unqualified=未达标,all/空=全部 + * @return 人员打卡情况全量列表 + */ + List userPunchExportData(String mainActivityId, String subActivityId, String activityId, String unionId, String searchKeyword, String qualifiedStatus); + + /** + * 导出所选主分活动的分工会报名与打卡人数。 + * + * @param response HTTP响应,用于写入XSSF格式Excel文件流 + * @param mainActivityId 主活动ID或relation:关联项目ID + * @param subActivityId 分活动ID,空值表示主活动汇总 + * @param activityId 健步走云端活动ID + * 无返回对象,通过response写出Excel文件流。 + */ + void exportUnionRegCheckInNum(HttpServletResponse response, String mainActivityId, String subActivityId, String activityId); + + /** + * 导出所选活动和分工会的报名人员明细。 + * + * @param response HTTP响应,用于写入XSSF格式Excel文件流 + * @param mainActivityId 主活动ID或relation:关联项目ID + * @param subActivityId 分活动ID,空值表示主活动汇总 + * @param activityId 健步走云端活动ID + * @param unionId 分工会ID,空值表示不追加筛选,已有数据权限仍生效 + * 无返回对象,通过response写出Excel文件流。 + */ + void exportRegUsers(HttpServletResponse response, String mainActivityId, String subActivityId, String activityId, String unionId); + + /** + * 导出所选活动和分工会的打卡点位完成明细。 + * + * @param response HTTP响应,用于写入XSSF格式Excel文件流 + * @param mainActivityId 主活动ID或relation:关联项目ID + * @param subActivityId 分活动ID,空值表示主活动汇总 + * @param activityId 健步走云端活动ID + * @param unionId 分工会ID,空值表示不追加筛选,已有数据权限仍生效 + * 无返回对象,通过response写出Excel文件流。 + */ + void exportCheckInUsers(HttpServletResponse response, String mainActivityId, String subActivityId, String activityId, String unionId); + + /** + * 导出所选活动中尚未取得礼品券的报名人员。 + * + * @param response HTTP响应,用于写入XSSF格式Excel文件流 + * @param mainActivityId 主活动ID或relation:关联项目ID + * @param subActivityId 分活动ID,空值表示主活动汇总 + * @param activityId 健步走云端活动ID + * 无返回对象,通过response写出Excel文件流。 + */ + void exportNotGetGiftVoucherUsers(HttpServletResponse response, String mainActivityId, String subActivityId, String activityId); + + /** + * 导出所选活动中尚未使用礼品券的人员。 + * + * @param response HTTP响应,用于写入XSSF格式Excel文件流 + * @param mainActivityId 主活动ID或relation:关联项目ID + * @param subActivityId 分活动ID,空值表示主活动汇总 + * @param activityId 健步走云端活动ID + * 无返回对象,通过response写出Excel文件流。 + */ + void exportNotUseGiftVoucherUsers(HttpServletResponse response, String mainActivityId, String subActivityId, String activityId); + + /** + * 按主分活动和达标筛选导出全部人员打卡汇总。 + * + * @param mainActivityId 主活动ID或relation:关联项目ID + * @param subActivityId 分活动ID,空值表示主活动汇总 + * @param activityId 健步走云端活动ID + * @param unionId 分工会ID,空值表示不追加筛选,已有数据权限仍生效 + * @param searchKeyword 姓名、工号或当前查询字段的检索内容 + * @param qualifiedStatus 达标筛选,qualified、unqualified或all + * @param response HTTP响应,用于写入XSSF格式Excel文件流 + * 无返回对象,通过response写出Excel文件流。 + */ + void exportUserPunchExcel(String mainActivityId, String subActivityId, String activityId, String unionId, String searchKeyword, String qualifiedStatus, + HttpServletResponse response); } diff --git a/src/main/java/com/budwk/app/zhgh/activity/fitnesswalk/service/FitnessWalkStepRaffleService.java b/src/main/java/com/budwk/app/zhgh/activity/fitnesswalk/service/FitnessWalkStepRaffleService.java index f3d24fb..87a1d80 100644 --- a/src/main/java/com/budwk/app/zhgh/activity/fitnesswalk/service/FitnessWalkStepRaffleService.java +++ b/src/main/java/com/budwk/app/zhgh/activity/fitnesswalk/service/FitnessWalkStepRaffleService.java @@ -1,13 +1,15 @@ package com.budwk.app.zhgh.activity.fitnesswalk.service; import com.budwk.app.base.param.PageForm; +import com.budwk.app.base.result.Result; import com.budwk.app.base.service.BaseService; import com.budwk.app.zhgh.activity.fitnesswalk.model.FitnessWalkRaffleRules; +import com.budwk.app.zhgh.activity.fitnesswalk.service.FitnessWalkStepRaffleService; +import java.util.List; +import javax.servlet.http.HttpServletResponse; import org.nutz.dao.sql.Sql; import org.nutz.lang.util.NutMap; -import java.util.List; - /** * @version 1.0 * @Author zzr @@ -31,4 +33,94 @@ public interface FitnessWalkStepRaffleService extends BaseService implements FitnessWalkCommonService { + @Inject + private BaseService baseService; + @Inject private RedisService redisService; @@ -53,14 +85,15 @@ public class FitnessWalkCommonServiceImpl extends BaseServiceImpl urlStr = new ArrayList<>(); for (String url : activity.getRandomPics()) { @@ -86,7 +120,7 @@ public class FitnessWalkCommonServiceImpl extends BaseServiceImpl JSONUtil.toBean((String) v, FitnessWalkActivity.class)) - .findFirst().orElse(null); + activity = jsonObject.getJSONArray("data").stream().map(v -> JSON.parseObject((String) v, FitnessWalkActivity.class)).findFirst().orElse(null); if (Lang.isNotEmpty(activity.getRandomPics())) { List urlStr = new ArrayList<>(); for (String url : activity.getRandomPics()) { @@ -125,54 +157,57 @@ public class FitnessWalkCommonServiceImpl extends BaseServiceImpl getAllActivity() { List fitnessWalkActivities; if (redisService.exists("FitnessWalk:ActivityList")) { String dataString = redisService.get("FitnessWalk:ActivityList"); - fitnessWalkActivities = JSONUtil.toList(dataString, FitnessWalkActivity.class); + fitnessWalkActivities = JSONObject.parseArray(dataString, FitnessWalkActivity.class); } else { StringBuilder sql = new StringBuilder(); sql.append("db.collection('activity').field({") .append("masterActivityName:true,") .append("branchActivityName:true,") + .append("relationActivityId:true,") .append("activityModel:true,") + .append("createdAt:true,") .append("endTime:true,") .append("startTime:true,") .append("unionId:true") .append("}).orderBy('startTime', 'desc').get()"); JSONObject jsonObject = weAppCloudUtil.request(WeAppCloudUtil.CRUD.QUERY, sql.toString()); - fitnessWalkActivities = jsonObject.getJSONArray("data").stream() - .map(v -> JSONUtil.toBean((String) v, FitnessWalkActivity.class)).collect(Collectors.toList()); + fitnessWalkActivities = jsonObject.getJSONArray("data").stream().map(v -> JSON.parseObject((String) v, FitnessWalkActivity.class)).collect(Collectors.toList()); } - if (AuthUtil.hasRoleOr(RoleConstant.BRANCH_UNION_CHAIRMAN.name(), RoleConstant.BRANCH_UNION_OPERATOR.name(), RoleConstant.BRANCH_UNION_ADMIN.name()) + redisService.set("FitnessWalk:ActivityList", JSONObject.toJSONString(fitnessWalkActivities)); + // 分工会可见范围只应用于已登录的PC请求,小程序匿名接口和后台任务不读取PC会话。 + if (Mvcs.getReq() != null && StpUtil.isLogin() && AuthUtil.hasRoleOr(RoleConstant.BRANCH_UNION_CHAIRMAN.name(), RoleConstant.BRANCH_UNION_OPERATOR.name(), RoleConstant.BRANCH_UNION_ADMIN.name()) && !AuthUtil.hasRoleOr(RoleConstant.SYSADMIN.name(), RoleConstant.SCHOOL_UNION_ADMIN.name())) { fitnessWalkActivities = fitnessWalkActivities.stream() .filter(v -> SecurityUtil.getUnionId().equals(v.getUnionId())) .collect(Collectors.toList()); } - redisService.set("FitnessWalk:ActivityList", JSONUtil.toJsonStr(fitnessWalkActivities)); return fitnessWalkActivities; } - @Override + @Aop(TransAop.READ_COMMITTED) public List cascaderActivity(int year, FitnessWalkMode mode) { Calendar calendar = Calendar.getInstance(); @@ -206,13 +241,15 @@ public class FitnessWalkCommonServiceImpl extends BaseServiceImpl oldTasks = sysTaskService.dao().query(Sys_task.class, Cnd.where("data", "like", "%" + fitnessWalkActivity.get_id() + "%")); @@ -221,7 +258,6 @@ public class FitnessWalkCommonServiceImpl extends BaseServiceImpl map = new HashMap<>(5); map.put("id", fitnessWalkActivity.get_id()); // 唯一标识的删除id,用于执行定时任务完成后,将此定时任务禁用 @@ -278,7 +314,7 @@ public class FitnessWalkCommonServiceImpl extends BaseServiceImpl map = new HashMap<>(8); map.put("id", fitnessWalkActivity.get_id()); // 唯一标识的删除id,用于执行定时任务完成后,将此定时任务禁用 @@ -315,19 +351,498 @@ public class FitnessWalkCommonServiceImpl extends BaseServiceImpl oldTasks = sysTaskService.query(Cnd.where("note", "=", fitnessWalkActivity.get_id()).and("jobClass", "=", jobClass)); + DateTime todayStart = DateUtil.beginOfDay(DateUtil.date()); + Set lockedTargetDateSet = new HashSet<>(); + + // 编辑活动时,已经执行过或已经生成过活动的历史日期不再重建,避免把旧数据重新覆盖。 + for (Sys_task task : oldTasks) { + Long targetDate = getCreateTaskTargetDate(task); + if (targetDate != null && shouldKeepHistoricalCreateTask(task, targetDate, todayStart.getTime(), fitnessWalkActivity.get_id())) { + lockedTargetDateSet.add(targetDate); + continue; + } + taskPlatformService.delete(task.getId(), task.getId()); + sysTaskService.delete(task.getId()); + } + + if (!Boolean.TRUE.equals(fitnessWalkActivity.getAutoContinuousCreate()) + || fitnessWalkActivity.getContinuousCreateDate() == null + || fitnessWalkActivity.getContinuousCreateDate().length < 2 + || fitnessWalkActivity.getContinuousCreateDate()[0] == null + || fitnessWalkActivity.getContinuousCreateDate()[1] == null) { + return; + } + + DateTime startDate = DateUtil.beginOfDay(DateUtil.date(fitnessWalkActivity.getContinuousCreateDate()[0])); + DateTime endDate = DateUtil.beginOfDay(DateUtil.date(fitnessWalkActivity.getContinuousCreateDate()[1])); + if (startDate.isAfter(endDate)) { + return; + } + + DateTime now = DateUtil.date(); + for (DateTime currentDate = startDate; !currentDate.isAfter(endDate); currentDate = DateUtil.offsetDay(currentDate, 1)) { + long targetDate = currentDate.getTime(); + // 今天及以前的活动日期视为历史数据;已经落地的日期和已锁定的旧任务都不再改动。 + if (targetDate <= todayStart.getTime() + || lockedTargetDateSet.contains(targetDate) + || hasCreatedActivityForTargetDate(fitnessWalkActivity.get_id(), targetDate)) { + continue; + } + + // 每个目标活动日期都在前一天 23:59 触发创建任务。 + DateTime executeTime = DateUtil.endOfDay(DateUtil.offsetDay(currentDate, -1)); + executeTime.setField(DateField.MINUTE, 59); + executeTime.setField(DateField.SECOND, 0); + executeTime.setField(DateField.MILLISECOND, 0); + + if (!executeTime.isAfter(now)) { + continue; + } + // 每天生成一个定时任务,执行时按原活动内容复制出当天的新活动。 + Map taskData = new HashMap<>(4); + taskData.put("sourceActivityId", fitnessWalkActivity.get_id()); + taskData.put("targetDate", targetDate); + taskData.put("deleteId", R.UU32()); + taskData.put("activityName", fitnessWalkActivity.getMasterActivityName()); + + Sys_task task = new Sys_task(); + task.setName(fitnessWalkActivity.getMasterActivityName() + "自动创建活动任务"); + task.setJobClass(jobClass); + // 任务说明直接保存原活动ID,便于编辑和删除活动时统一清理相关任务。 + task.setNote(fitnessWalkActivity.get_id()); + task.setCron(new SimpleDateFormat("ss mm HH dd MM ? yyyy").format(executeTime)); + task.setData(Json.toJson(taskData)); + task.setDisabled(false); + sysTaskService.insert(task); + taskPlatformService.add(task.getId(), task.getId(), task.getJobClass(), task.getCron(), task.getNote(), task.getData()); + } + } + + /** + * 定时任务数据里会保存目标活动日期,编辑活动时要靠这个日期判断哪些任务已经属于历史数据。 + * 这里统一从任务 data 中解析 targetDate,并归一化到当天 00:00:00,避免比较时出现毫秒偏差。 + */ + private Long getCreateTaskTargetDate(Sys_task task) { + if (task == null || Strings.isBlank(task.getData())) { + return null; + } + try { + JSONObject data = JSONObject.parseObject(task.getData()); + Object targetDateObj = data.get("targetDate"); + if (targetDateObj == null) { + return null; + } + long targetDate = targetDateObj instanceof Number + ? ((Number) targetDateObj).longValue() + : Long.parseLong(String.valueOf(targetDateObj)); + return DateUtil.beginOfDay(DateUtil.date(targetDate)).getTime(); + } catch (Exception e) { + return null; + } + } + + /** + * 编辑原活动时,已经执行过的自动创建任务和已经生成过活动的日期都要保留。 + * 这样可以确保用户修改未来配置时,不会把过去已经存在的活动和任务重新删除或覆盖。 + */ + private boolean shouldKeepHistoricalCreateTask(Sys_task task, long targetDate, long todayStart, String sourceActivityId) { + return task.isDisabled() + || targetDate <= todayStart + || hasCreatedActivityForTargetDate(sourceActivityId, targetDate); + } + + /** + * 自动创建出的活动会带上来源活动ID和目标活动日期。 + * 编辑原活动时通过这两个字段判断某一天的活动是否已经落地,避免重复建任务或重复建活动。 + */ + private boolean hasCreatedActivityForTargetDate(String sourceActivityId, long targetDate) { + if (Strings.isBlank(sourceActivityId)) { + return false; + } + StringBuilder sql = new StringBuilder("db.collection('activity').where({autoCreateSourceActivityId:'"); + sql.append(sourceActivityId) + .append("',autoCreateTargetDate:") + .append(targetDate) + .append("}).count()"); + JSONObject jsonObject = weAppCloudUtil.request(WeAppCloudUtil.CRUD.QUERY, sql.toString()); + JSONObject pager = jsonObject.getJSONObject("pager"); + return pager != null && pager.getIntValue("Total") > 0; + } @Override - public Pagination getStepRankingPagination(PageForm pageForm, String activityId, String unionId, String unitId, String mode) { - Sql sql = rankingListSql(pageForm.getSearchName(), pageForm.getSearchKeyword(), activityId, unionId, unitId, mode); + public Pagination getStepRankingPagination(PageForm pageForm, + String activityId, + String unionId, + String unitId, + String mode, + String sex, + String ageRange) { + Sql sql = rankingListSql( + pageForm.getSearchName(), + pageForm.getSearchKeyword(), + activityId, + unionId, + unitId, + mode, + sex, + ageRange + ); return listPageMap(pageForm.getPageNumber(), pageForm.getPageSize(), sql); } @Override - public List getStepRankingList(String searchName, String searchKeyword, String activityId, String unionId, String unitId, String mode) { - Sql sql = rankingListSql(searchName, searchKeyword, activityId, unionId, unitId, mode); + public List getStepRankingList(String searchName, + String searchKeyword, + String activityId, + String unionId, + String unitId, + String mode, + String sex, + String ageRange) { + Sql sql = rankingListSql(searchName, searchKeyword, activityId, unionId, unitId, mode, sex, ageRange); return listMap(sql); } + /** + * 查询小程序筛选排行榜。查询先按用户和日期保留最大步数,避免同一天重复上传造成累计值偏高; + * 再在活动有效时间范围内根据性别和年龄段汇总累计步数、目标完成率及达标天数。 + * + * @param activityId 活动ID + * @param userId 当前登录用户ID,用于定位当前用户行和名次 + * @param rankingType totalSteps、completionRate 或 complianceDays + * @param sex 空字符串、男或女 + * @param ageRange 空字符串、under40、41to50、51to55 或 over55;over55 表示年龄大于55岁 + * @param timeRange today、oneMonth、halfYear、oneYear 或 activity + * @param pageNumber 页码,从1开始 + * @param pageSize 每页条数 + * @return list 为当前页数据,currentUser 为当前用户指标,其他字段为分页信息 + */ + @Override + public NutMap getUserStepRanking(String activityId, + String userId, + String rankingType, + String sex, + String ageRange, + String timeRange, + Integer pageNumber, + Integer pageSize) { + FitnessWalkActivity activity = getRankingActivity(activityId); + if (activity == null || activity.getStartTime() == null || activity.getEndTime() == null) { + throw new IllegalArgumentException("活动不存在或活动时间配置不完整"); + } + + DateTime activityStartDate = DateUtil.beginOfDay(DateUtil.date(activity.getStartTime())); + DateTime todayStartDate = DateUtil.beginOfDay(new Date()); + DateTime queryStartDate = todayStartDate; + if ("oneMonth".equals(timeRange)) { + queryStartDate = DateUtil.beginOfDay(DateUtil.offsetMonth(todayStartDate, -1)); + } else if ("halfYear".equals(timeRange)) { + queryStartDate = DateUtil.beginOfDay(DateUtil.offsetMonth(todayStartDate, -6)); + } else if ("oneYear".equals(timeRange)) { + queryStartDate = DateUtil.beginOfDay(DateUtil.offset(todayStartDate, DateField.YEAR, -1)); + } else if ("activity".equals(timeRange)) { + queryStartDate = activityStartDate; + } + // 所有时间筛选都必须限定在活动有效期内,避免统计活动开始前上传的步数。 + if (queryStartDate.isBefore(activityStartDate)) { + queryStartDate = activityStartDate; + } + DateTime queryEndDate = DateUtil.endOfDay(new Date()); + DateTime activityEndDate = DateUtil.endOfDay(DateUtil.date(activity.getEndTime())); + if (queryEndDate.isAfter(activityEndDate)) { + queryEndDate = activityEndDate; + } + + if (queryStartDate.isAfter(queryEndDate)) { + return emptyRankingResult(pageNumber, pageSize, userId); + } + + int targetStep = resolveRankingTargetStep(activity, rankingType, queryStartDate, queryEndDate); + long periodDays = Math.max(1L, DateUtil.between(queryStartDate, queryEndDate, DateUnit.DAY) + 1L); + + List filteredRows = queryRankingRows( + activityId, + rankingType, + sex, + ageRange, + null, + queryStartDate, + queryEndDate, + targetStep, + periodDays + ); + + List rankingRows = new ArrayList<>(filteredRows.size()); + NutMap currentUser = null; + NutMap previousRow = null; + Integer previousRank = null; + for (int index = 0; index < filteredRows.size(); index++) { + NutMap row = filteredRows.get(index); + // 三项排名指标完全相同时沿用上一名次;指标不同时在上一名次基础上递增,形成 1、2、2、3 的稠密排名。 + Integer rank = previousRow != null && hasSameRankingMetrics(previousRow, row) + ? previousRank + : previousRank == null ? 1 : previousRank + 1; + NutMap rankingRow = normalizeRankingRow(row, rank, userId, rankingType); + rankingRows.add(rankingRow); + if (Boolean.TRUE.equals(rankingRow.getBoolean("currentUser"))) { + currentUser = rankingRow; + } + previousRow = row; + previousRank = rank; + } + + // 当前用户不符合所选性别或年龄段时仍返回个人指标,但不伪造其在当前筛选范围内的名次。 + if (currentUser == null) { + List currentUserRows = queryRankingRows( + activityId, + rankingType, + null, + null, + userId, + queryStartDate, + queryEndDate, + targetStep, + periodDays + ); + if (Lang.isNotEmpty(currentUserRows)) { + currentUser = normalizeRankingRow(currentUserRows.get(0), null, userId, rankingType); + } else { + Sys_user user = sysUserService.fetch(userId); + currentUser = NutMap.NEW() + .addv("userId", userId) + .addv("username", user == null ? "我" : user.getUsername()) + .addv("rank", null) + .addv("value", 0) + .addv("currentUser", true); + } + } + + int totalCount = rankingRows.size(); + int totalPage = totalCount == 0 ? 0 : (int) Math.ceil(totalCount * 1.0D / pageSize); + int fromIndex = Math.min((pageNumber - 1) * pageSize, totalCount); + int toIndex = Math.min(fromIndex + pageSize, totalCount); + List pageList = new ArrayList<>(rankingRows.subList(fromIndex, toIndex)); + + return NutMap.NEW() + .addv("list", pageList) + .addv("pageNumber", pageNumber) + .addv("pageSize", pageSize) + .addv("totalCount", totalCount) + .addv("totalPage", totalPage) + .addv("currentUser", currentUser); + } + + /** + * 读取排行榜计算所需的活动配置。排行榜不需要封面、证书等临时文件地址, + * 因此缓存命中时直接反序列化活动数据,避免重复访问微信云数据库和文件接口。 + * + * @param activityId 活动ID,对应 Redis 中的 FitnessWalk:{activityId} + * @return 活动配置;缓存不存在时回退到现有活动查询逻辑并自动写入缓存 + */ + private FitnessWalkActivity getRankingActivity(String activityId) { + String activityJson = redisService.get("FitnessWalk:" + activityId); + if (Strings.isNotBlank(activityJson)) { + return JSONObject.parseObject(activityJson, FitnessWalkActivity.class); + } + return getActivity(activityId); + } + + /** + * 获取活动达标步数。普通计步活动优先使用 lotteryQualificationSteps; + * 每日或自定义抽奖活动则使用对应规则中的有效步数,无法取得时不允许计算完成率和达标天数。 + */ + private int resolveRankingTargetStep(FitnessWalkActivity activity, + String rankingType, + Date queryStartDate, + Date queryEndDate) { + Integer targetStep = activity.getLotteryQualificationSteps(); + if (targetStep == null || targetStep <= 0) { + FitnessWalkActivity.DayLotteryRule dayLotteryRule = activity.getDayLotteryRule(); + if (dayLotteryRule != null && dayLotteryRule.getLotteryQualificationStep() != null + && dayLotteryRule.getLotteryQualificationStep() > 0) { + targetStep = dayLotteryRule.getLotteryQualificationStep(); + } + } + if ((targetStep == null || targetStep <= 0) && Lang.isNotEmpty(activity.getCustomLotteryRules())) { + for (FitnessWalkActivity.CustomLotteryRule rule : activity.getCustomLotteryRules()) { + if (rule.getLotteryQualificationStep() == null || rule.getLotteryQualificationStep() <= 0) { + continue; + } + boolean hasStartOverlap = rule.getEndDate() == null || !rule.getEndDate().before(queryStartDate); + boolean hasEndOverlap = rule.getStartDate() == null || !rule.getStartDate().after(queryEndDate); + if (hasStartOverlap && hasEndOverlap) { + targetStep = rule.getLotteryQualificationStep(); + break; + } + } + } + + if (targetStep == null || targetStep <= 0) { + if ("totalSteps".equals(rankingType)) { + return 1; + } + throw new IllegalArgumentException("当前活动未配置有效达标步数,无法计算该排行榜"); + } + return targetStep; + } + + /** + * 执行排行榜聚合查询。userId 不为空时仅查询当前用户,用于其被筛选条件排除后的个人指标展示。 + */ + private List queryRankingRows(String activityId, + String rankingType, + String sex, + String ageRange, + String userId, + Date queryStartDate, + Date queryEndDate, + int targetStep, + long periodDays) { + // 所选指标作为第一排序条件,另外两项依次打破同分;用户ID只保证完全并列数据的返回顺序稳定。 + String orderSql = "total_steps DESC, completion_rate DESC, compliance_days DESC, u.id ASC"; + if ("completionRate".equals(rankingType)) { + orderSql = "completion_rate DESC, compliance_days DESC, total_steps DESC, u.id ASC"; + } else if ("complianceDays".equals(rankingType)) { + orderSql = "compliance_days DESC, completion_rate DESC, total_steps DESC, u.id ASC"; + } + + String ageCondition = ""; + if ("under40".equals(ageRange)) { + ageCondition = "AND u.birthday IS NOT NULL AND TIMESTAMPDIFF(YEAR, u.birthday, CURDATE()) <= 40"; + } else if ("41to50".equals(ageRange)) { + ageCondition = "AND u.birthday IS NOT NULL AND TIMESTAMPDIFF(YEAR, u.birthday, CURDATE()) BETWEEN 41 AND 50"; + } else if ("51to55".equals(ageRange)) { + ageCondition = "AND u.birthday IS NOT NULL AND TIMESTAMPDIFF(YEAR, u.birthday, CURDATE()) BETWEEN 51 AND 55"; + } else if ("over55".equals(ageRange)) { + ageCondition = "AND u.birthday IS NOT NULL AND TIMESTAMPDIFF(YEAR, u.birthday, CURDATE()) > 55"; + } + + Sql sql = Sqls.create(""" + SELECT + u.id AS user_id, + u.username, + u.sex, + TIMESTAMPDIFF(YEAR, u.birthday, CURDATE()) AS age, + COALESCE(SUM(d.daily_step), 0) AS total_steps, + LEAST(100, ROUND(SUM(LEAST(d.daily_step, @targetStep)) * 100.0 / (@targetStep * @periodDays), 0)) AS completion_rate, + SUM(CASE WHEN d.daily_step >= @targetStep THEN 1 ELSE 0 END) AS compliance_days + FROM ( + SELECT + userId, + applyDate, + MAX(step) AS daily_step + FROM fitness_walk_step + WHERE activityId = @activityId + AND applyDate >= @startDate + AND applyDate <= @endDate + GROUP BY userId, applyDate + ) d + INNER JOIN sys_user u ON u.id = d.userId + WHERE 1 = 1 + AND (@sex IS NULL OR u.sex = @sex) + $ageCondition + AND (@userId IS NULL OR u.id = @userId) + GROUP BY u.id, u.username, u.sex, u.birthday + HAVING COALESCE(SUM(d.daily_step), 0) > 0 + ORDER BY $orderSql + """); + sql.setParam("activityId", activityId); + sql.setParam("startDate", queryStartDate); + sql.setParam("endDate", queryEndDate); + sql.setParam("targetStep", targetStep); + sql.setParam("periodDays", periodDays); + // 性别和用户ID必须直接出现在原始SQL中,确保Nutz能够识别并绑定命名参数;空值表示不启用对应筛选。 + sql.setParam("sex", Strings.isBlank(sex) ? null : sex); + sql.setParam("userId", Strings.isBlank(userId) ? null : userId); + sql.setVar("ageCondition", new Static(ageCondition)); + sql.setVar("orderSql", new Static(orderSql)); + return listMap(sql); + } + + /** + * 将数据库聚合字段转换成前端统一使用的排名结构。 + */ + private NutMap normalizeRankingRow(NutMap row, Integer rank, String currentUserId, String rankingType) { + String rowUserId = row.getString("user_id"); + Integer age = row.getInt("age"); + Number value; + if ("completionRate".equals(rankingType)) { + value = defaultNumber(row.get("completion_rate")); + } else if ("complianceDays".equals(rankingType)) { + value = defaultNumber(row.get("compliance_days")); + } else { + value = defaultNumber(row.get("total_steps")); + } + + return NutMap.NEW() + .addv("userId", rowUserId) + .addv("username", row.getString("username")) + .addv("sex", row.getString("sex")) + .addv("age", age) + .addv("ageGroup", getAgeGroup(age)) + .addv("rank", rank) + .addv("value", value) + .addv("currentUser", Objects.equals(rowUserId, currentUserId)); + } + + private Number defaultNumber(Object value) { + return value instanceof Number ? (Number) value : 0; + } + + /** + * 判断两位用户的三个排名指标是否完全相同。只有完成率、达标天数和累计步数均相同才并列排名。 + */ + private boolean hasSameRankingMetrics(NutMap previousRow, NutMap currentRow) { + return Double.compare( + defaultNumber(previousRow.get("completion_rate")).doubleValue(), + defaultNumber(currentRow.get("completion_rate")).doubleValue() + ) == 0 && Double.compare( + defaultNumber(previousRow.get("compliance_days")).doubleValue(), + defaultNumber(currentRow.get("compliance_days")).doubleValue() + ) == 0 && Double.compare( + defaultNumber(previousRow.get("total_steps")).doubleValue(), + defaultNumber(currentRow.get("total_steps")).doubleValue() + ) == 0; + } + + private String getAgeGroup(Integer age) { + if (age == null) { + return "年龄未知"; + } + if (age <= 40) { + return "40岁以下"; + } + if (age <= 50) { + return "41-50岁"; + } + if (age <= 55) { + return "51-55岁"; + } + return "55岁以上"; + } + + private NutMap emptyRankingResult(Integer pageNumber, Integer pageSize, String userId) { + return NutMap.NEW() + .addv("list", new ArrayList<>()) + .addv("pageNumber", pageNumber) + .addv("pageSize", pageSize) + .addv("totalCount", 0) + .addv("totalPage", 0) + .addv("currentUser", NutMap.NEW() + .addv("userId", userId) + .addv("rank", null) + .addv("value", 0) + .addv("currentUser", true)); + } /** * 获取中奖榜 @@ -400,7 +915,6 @@ public class FitnessWalkCommonServiceImpl extends BaseServiceImpl customLotteryRules = activity.getCustomLotteryRules(); @@ -414,10 +928,48 @@ public class FitnessWalkCommonServiceImpl extends BaseServiceImpl= '" - +DateUtil.format(DateUtil.date(activity.getStartTime()), "yyyy-MM-dd")+"' and DATE(applyDate) <= '" - +DateUtil.format(DateUtil.date(activity.getEndTime()), "yyyy-MM-dd")+"'")); -// cnd.and("DATE( s.applyDate )", ">=", DateUtil.format(DateUtil.date(activity.getStartTime()), "yyyy-MM-dd")); -// cnd.and("DATE( s.applyDate )", "<=", DateUtil.format(DateUtil.date(activity.getEndTime()), "yyyy-MM-dd")); + DateTime activityStartDate = DateUtil.beginOfDay(DateUtil.date(activity.getStartTime())); + DateTime activityEndDate = DateUtil.endOfDay(DateUtil.date(activity.getEndTime())); + DateTime todayStartDate = DateUtil.beginOfDay(new Date()); + DateTime queryStartDate = todayStartDate; + if ("oneMonth".equals(mode)) { + queryStartDate = DateUtil.beginOfDay(DateUtil.offsetMonth(todayStartDate, -1)); + } else if ("halfYear".equals(mode)) { + queryStartDate = DateUtil.beginOfDay(DateUtil.offsetMonth(todayStartDate, -6)); + } else if ("oneYear".equals(mode)) { + queryStartDate = DateUtil.beginOfDay(DateUtil.offset(todayStartDate, DateField.YEAR, -1)); + } else if ("activity".equals(mode) || "all".equals(mode)) { + queryStartDate = activityStartDate; } + // PC端所有时间范围都不得超出活动有效期,查询结束时间取今天和活动结束时间中的较早值。 + if (queryStartDate.isBefore(activityStartDate)) { + queryStartDate = activityStartDate; + } + DateTime queryEndDate = DateUtil.endOfDay(new Date()); + if (queryEndDate.isAfter(activityEndDate)) { + queryEndDate = activityEndDate; + } + String dateSql = "applyDate >= '" + DateUtil.format(queryStartDate, "yyyy-MM-dd HH:mm:ss") + + "' AND applyDate <= '" + DateUtil.format(queryEndDate, "yyyy-MM-dd HH:mm:ss") + "'"; + sql.setVar("dateSql", new Static(dateSql)); + if (Strings.isNotBlank(unitId)) { cnd.and("u.unitId", "=", unitId); } if (Strings.isNotBlank(unionId)) { cnd.and("u.unionId", "=", unionId); } + if (Strings.isNotBlank(sex)) { + cnd.and("u.sex", "=", sex); + } + + // 年龄筛选与移动端排行榜保持同一分段;生日为空时不会命中任一指定年龄段。 + String ageExpression = "TIMESTAMPDIFF(YEAR, u.birthday, CURDATE())"; + if ("under40".equals(ageRange)) { + cnd.and(ageExpression, "<=", 40); + } else if ("41to50".equals(ageRange)) { + cnd.and(ageExpression, ">=", 41); + cnd.and(ageExpression, "<=", 50); + } else if ("51to55".equals(ageRange)) { + cnd.and(ageExpression, ">=", 51); + cnd.and(ageExpression, "<=", 55); + } else if ("over55".equals(ageRange)) { + cnd.and(ageExpression, ">", 55); + } if (Strings.isNotBlank(searchName) && Strings.isNotBlank(searchKeyword)) { cnd.and(searchName, "LIKE", "%" + searchKeyword + "%"); } // cnd.and("u.id","in",Sqls.create("select userId from activity_user_scope where groupId = @groupId").setParam("groupId",activity.getGroupId())); - cnd.groupBy("u.id","u.username","u.loginname","u.unionName","u.unitName","over3k.standardsDays"); + cnd.groupBy("u.id","u.username","u.loginname","u.sex","u.birthday","u.unionname","u.unitname","over3k.standardsDays"); cnd.desc("total_steps"); cnd.having(Cnd.NEW().and("COALESCE(SUM(sub.step), 0)",">",0)); sql.setCondition(cnd); return sql; } + + /** + * 按姓名和工号匹配小程序用户并返回单位及角色资料。 + * + * @param username 用户姓名,与工号共同匹配人员 + * @param loginname 用户工号 + * @return Result,code为0表示成功,data承载按姓名和工号匹配小程序用户并返回单位及角色资料结果,失败说明见msg + */ + @Override + public Result login(String username, String loginname) { + try { + Sql sql = Sqls.create(""" + SELECT + u.id userid, + u.username, + u.loginname, + u.sex, + u.mobile, + u.birthday, + unit.`name` unitname, + u.unitid, + u.member, + unit.unionid, + un.name unionname, + group_concat(sr.`code`) roles + FROM + sys_user u + LEFT JOIN sys_unit unit ON u.unitid = unit.id + LEFT JOIN sys_union un ON unit.unionid = un.id + LEFT JOIN sys_user_role sur ON sur.userId = u.id + LEFT JOIN sys_role sr ON sr.id = sur.roleId + $condition + """).setParam("loginname", loginname).setParam("username", username); + + Cnd cnd = Cnd.NEW(); + cnd.and("LOWER(u.loginname)", "=", loginname.trim().toLowerCase()); + cnd.and("LOWER(u.username)", "=", username.trim().toLowerCase()); + sql.setCondition(cnd); + sql.setCallback(Sqls.callback.map()); + sysUserService.dao().execute(sql); + NutMap user = (NutMap) sql.getResult(); + if (StrUtil.isBlank(user.getString("userid"))) { + return Result.error().addMsg("系统查询不到您的信息,请与工会管理员联系!"); + } +// if(user.getInt("member",0)==0){ +// return Result.error().addMsg("您没有权限参加本次健步行,请与工会管理员联系!"); +// } + /*if (user.getInt("member", 0) != 1 && !user.getString("sex", "").equals("女")) { + return Result.error().addMsg("您没有权限参加本次健步行,请与工会管理员联系!"); + }*/ + return Result.success().addData(user); + } catch (Exception e) { + return Result.error(); + } + } + + /** + * 导出活动有效期内的参与人员累计步数。 + * + * @param searchName 查询字段,使用页面提供的字段名 + * @param searchKeyword 姓名、工号或当前查询字段的检索内容 + * @param activityId 健步走云端活动ID + * @param unionId 分工会ID,空值表示不追加筛选,已有数据权限仍生效 + * @param unitId 单位ID,空值表示不追加单位筛选 + * @param response HTTP响应,用于写入XSSF格式Excel文件流 + * 无返回对象,通过response写出Excel文件流。 + */ + @Override + @Ok("void") + public void exportTotalSteps(String searchName, + String searchKeyword, + String activityId, + String unionId, + String unitId, + HttpServletResponse response) { + FitnessWalkActivity activity = this.getActivity(activityId); + CndPlus cnd = CndPlus.create(); + Sql sql = Sqls.create(""" + SELECT + u.id, + COALESCE(SUM(fws.step), 0) AS total_steps, + u.username, + u.loginname, + u.unionname, + u.unitname + FROM + fitness_walk_step fws + LEFT JOIN vw_user u ON u.id = fws.userId + $condition + """); + + cnd.and("fws.activityId", "=", activityId); + cnd.andEX("DATE(fws.applyDate)", ">=", DateUtil.format(DateUtil.date(activity.getStartTime()), "yyyy-MM-dd")); + cnd.andEX("DATE(fws.applyDate)", "<=", DateUtil.format(DateUtil.date(activity.getEndTime()), "yyyy-MM-dd")); + cnd.andEX("u.unionId", "=", unionId); + cnd.andEX("u.unitId", "=", unitId); + cnd.groupBy("fws.userId"); + cnd.desc("total_steps"); + sql.setCondition(cnd); + + List stepRankingList = baseService.listMap(sql); + + List entityList = new ArrayList<>(); + ExcelExportEntity no = new ExcelExportEntity("序号", "no", 10); + no.setFormat("isAddIndex"); + entityList.add(no); + entityList.add(new ExcelExportEntity("姓名", "username", 20)); + entityList.add(new ExcelExportEntity("工号", "loginname", 20)); + entityList.add(new ExcelExportEntity("分工会", "unionname", 20)); + entityList.add(new ExcelExportEntity("单位", "unitname", 20)); + entityList.add(new ExcelExportEntity("活动期间总步数", "total_steps", 20)); + try { + ExportParams exportParams = new ExportParams(); + exportParams.setType(ExcelType.XSSF); + Workbook workbook = ExcelExportUtil.exportExcel(exportParams, entityList, stepRankingList); + CommonDownloadUtil.download("活动期间总步数榜.xlsx", workbook, response); + } catch (Exception e) { + e.printStackTrace(); + } + } + + /** + * 按时间、性别和年龄筛选导出当前步数排行榜。 + * + * @param searchName 查询字段,使用页面提供的字段名 + * @param searchKeyword 姓名、工号或当前查询字段的检索内容 + * @param activityId 健步走云端活动ID + * @param unionId 分工会ID,空值表示不追加筛选,已有数据权限仍生效 + * @param unitId 单位ID,空值表示不追加单位筛选 + * @param mode 时间范围:today、oneMonth、halfYear、oneYear或activity + * @param sex 性别筛选,男、女或空字符串 + * @param ageRange 年龄段筛选,under40、41to50、51to55、over55或空字符串 + * @param response HTTP响应,用于写入XSSF格式Excel文件流 + * 无返回对象,通过response写出Excel文件流。 + */ + @Override + @Ok("void") + public void exportStepRanking(String searchName, + String searchKeyword, + String activityId, + String unionId, + String unitId, + String mode, + String sex, + String ageRange, + HttpServletResponse response) { + if (Strings.isNotBlank(sex) && !List.of("男", "女").contains(sex)) { + throw new IllegalArgumentException("性别筛选参数不正确"); + } + if (Strings.isNotBlank(ageRange) && !List.of("under40", "41to50", "51to55", "over55").contains(ageRange)) { + throw new IllegalArgumentException("年龄段筛选参数不正确"); + } + String actualMode = Strings.isBlank(mode) ? "today" : mode; + if (!List.of("today", "oneMonth", "halfYear", "oneYear", "activity", "all").contains(actualMode)) { + throw new IllegalArgumentException("时间范围参数不正确"); + } + List stepRankingList = this.getStepRankingList( + searchName, + searchKeyword, + activityId, + unionId, + unitId, + actualMode, + sex, + ageRange + ); + + List entityList = new ArrayList<>(); + ExcelExportEntity no = new ExcelExportEntity("排名", "no", 10); + no.setFormat("isAddIndex"); + entityList.add(no); + entityList.add(new ExcelExportEntity("姓名", "username", 20)); + entityList.add(new ExcelExportEntity("工号", "loginname", 20)); + entityList.add(new ExcelExportEntity("性别", "sex", 10)); + entityList.add(new ExcelExportEntity("年龄", "age", 10)); + entityList.add(new ExcelExportEntity("分工会", "unionname", 20)); + entityList.add(new ExcelExportEntity("单位", "unitname", 20)); + entityList.add(new ExcelExportEntity("步数", "total_steps", 20)); + entityList.add(new ExcelExportEntity("达标天数", "standardsDays", 15)); + try { + String fileName = getRankingTimeRangeName(actualMode) + "步数榜.xlsx"; + ExportParams exportParams = new ExportParams(); + exportParams.setType(ExcelType.XSSF); + Workbook workbook = ExcelExportUtil.exportExcel(exportParams, entityList, stepRankingList); + CommonDownloadUtil.download(fileName, workbook, response); + } catch (Exception e) { + e.printStackTrace(); + } + } + + /** + * 将时间范围参数转换为排行榜导出文件名。 + * + * @param mode 时间范围:today、oneMonth、halfYear、oneYear或activity + * @return String,用于将时间范围参数转换为排行榜导出文件名 + */ + private String getRankingTimeRangeName(String mode) { + if ("oneMonth".equals(mode)) { + return "近1个月"; + } + if ("halfYear".equals(mode)) { + return "近半年"; + } + if ("oneYear".equals(mode)) { + return "近1年"; + } + if ("activity".equals(mode) || "all".equals(mode)) { + return "活动开始至今"; + } + return DateUtil.format(new Date(), "yyyy年MM月dd日"); + } + + /** + * 按活动抽奖规则导出各期中奖榜。 + * + * @param activityId 健步走云端活动ID + * @param response HTTP响应,用于写入XSSF格式Excel文件流 + * 无返回对象,通过response写出Excel文件流。 + */ + @Override + @Ok("void") + public void exportUserStepWining(String activityId, HttpServletResponse response) { + List winingRankingUserList = this.getWiningRankingUserList(activityId); + List entityList = new ArrayList<>(); + ExcelExportEntity no = new ExcelExportEntity("序号", "no", 10); + no.setFormat("isAddIndex"); + entityList.add(no); + entityList.add(new ExcelExportEntity("工号", "loginName", 20)); + entityList.add(new ExcelExportEntity("姓名", "username", 20)); + entityList.add(new ExcelExportEntity("分工会", "unionName", 20)); + entityList.add(new ExcelExportEntity("单位", "unitName", 40)); + entityList.add(new ExcelExportEntity("奖项名称", "awardName", 20)); + entityList.add(new ExcelExportEntity("步数", "awardSteps", 20)); + entityList.add(new ExcelExportEntity("中奖时间", "applyDate", 20)); + try { + response.setContentType("application/octet-stream"); + response.setHeader("content-disposition", "attachment;filename=" + + URLEncoder.encode("中奖明细.zip", "UTF-8")); + ZipOutputStream zipOutputStream = new ZipOutputStream(new BufferedOutputStream(response.getOutputStream())); + for (NutMap map : winingRankingUserList) { + List awardList = map.getAsList("awardList", NutMap.class); + awardList.forEach(v->v.setv("applyDate",DateUtil.format(v.getTime("applyDate"),"yyyy-MM-dd"))); + zipOutputStream.putNextEntry(new ZipEntry(map.getString("title") + ".xlsx")); + ExportParams exportParams = new ExportParams(); + exportParams.setType(ExcelType.XSSF); + Workbook workbook = ExcelExportUtil.exportExcel(exportParams, entityList, awardList); + workbook.write(zipOutputStream); + zipOutputStream.closeEntry(); + } + zipOutputStream.flush(); + zipOutputStream.close(); + } catch (Exception e) { + e.printStackTrace(); + } + + } + + /** + * 按活动、奖项和人员关键字检索中奖人员。 + * + * @param lotteryTime 中奖奖项筛选值,沿用页面提交格式 + * @param activityId 健步走云端活动ID + * @param searchKeyword 姓名、工号或当前查询字段的检索内容 + * @return Result,code为0表示成功,data承载按活动、奖项和人员关键字检索中奖人员结果,失败说明见msg + */ + @Override + public Result prizeUsers(String lotteryTime, String activityId, String searchKeyword) { + + CndPlus cnd = CndPlus.create(); + cnd.andEX("activityId", "=", activityId); + cnd.andEX("awardName", "=", lotteryTime); + if (StrUtil.isNotBlank(searchKeyword)) { + SqlExpressionGroup seg = new SqlExpressionGroup(); + seg.orLike("username", searchKeyword); + seg.orLike("loginName", searchKeyword); + cnd.and(seg); + } + cnd.and("isRead", "=", true); + List awardUserList = this.dao().query(FitnessWalkAwardUser.class, cnd); +// String lotteryDate = lotteryTime.substring(0, 10); +// List awardUserList = this.dao().query(FitnessWalkAwardUser.class, +// Cnd.where("activityId", "=", activityId).and("date(applyDate)", "=", lotteryDate)); + return Result.success(awardUserList); + } } diff --git a/src/main/java/com/budwk/app/zhgh/activity/fitnesswalk/service/impl/FitnessWalkPunchStatisticsServiceImpl.java b/src/main/java/com/budwk/app/zhgh/activity/fitnesswalk/service/impl/FitnessWalkPunchStatisticsServiceImpl.java index f80351b..d3ab046 100644 --- a/src/main/java/com/budwk/app/zhgh/activity/fitnesswalk/service/impl/FitnessWalkPunchStatisticsServiceImpl.java +++ b/src/main/java/com/budwk/app/zhgh/activity/fitnesswalk/service/impl/FitnessWalkPunchStatisticsServiceImpl.java @@ -1,25 +1,51 @@ package com.budwk.app.zhgh.activity.fitnesswalk.service.impl; +import cn.afterturn.easypoi.excel.ExcelExportUtil; +import cn.afterturn.easypoi.excel.entity.ExportParams; +import cn.afterturn.easypoi.excel.entity.enmus.ExcelType; +import cn.afterturn.easypoi.excel.entity.params.ExcelExportEntity; +import cn.hutool.core.collection.CollectionUtil; +import cn.hutool.core.date.DateUtil; import cn.hutool.core.util.StrUtil; -import cn.hutool.json.JSONObject; -import cn.hutool.json.JSONUtil; +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONObject; +import com.budwk.app.base.page.Pagination; import com.budwk.app.base.service.impl.BaseServiceImpl; +import com.budwk.app.base.utils.CommonDownloadUtil; import com.budwk.app.sys.models.Sys_union; +import com.budwk.app.sys.models.Sys_unit; +import com.budwk.app.zhgh.activity.fitnesswalk.contants.Cascader; +import com.budwk.app.zhgh.activity.fitnesswalk.contants.FitnessWalkMode; +import com.budwk.app.zhgh.activity.fitnesswalk.model.FitnessWalkActivity; +import com.budwk.app.zhgh.activity.fitnesswalk.model.FitnessWalkActivityRelationProject; import com.budwk.app.zhgh.activity.fitnesswalk.model.FitnessWalkAwardUser; +import com.budwk.app.zhgh.activity.fitnesswalk.service.FitnessWalkCommonService; import com.budwk.app.zhgh.activity.fitnesswalk.service.FitnessWalkPunchStatisticsService; import com.budwk.app.zhgh.activity.fitnesswalk.utils.WeAppCloudUtil; +import org.apache.poi.ss.usermodel.Workbook; +import org.nutz.aop.interceptor.ioc.TransAop; import org.nutz.dao.Cnd; import org.nutz.dao.Dao; +import org.nutz.ioc.aop.Aop; import org.nutz.ioc.loader.annotation.Inject; import org.nutz.ioc.loader.annotation.IocBean; +import org.nutz.lang.Strings; import org.nutz.lang.util.NutMap; - +import org.nutz.mvc.annotation.Ok; +import java.util.*; +import java.util.ArrayList; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.stream.Collectors; +import javax.servlet.http.HttpServletResponse; @IocBean(args = {"refer:dao"}) public class FitnessWalkPunchStatisticsServiceImpl extends BaseServiceImpl implements FitnessWalkPunchStatisticsService { + + private static final int EXPORT_QUERY_BATCH_SIZE = 500; + private static final String RELATION_ACTIVITY_PREFIX = "relation:"; + public FitnessWalkPunchStatisticsServiceImpl(Dao dao) { super(dao); } @@ -27,14 +53,64 @@ public class FitnessWalkPunchStatisticsServiceImpl extends BaseServiceImpl unionRegCheckInNum(String activityId, String unionId) { + @Aop(TransAop.READ_COMMITTED) + public List userPunchCascaderActivity(int year) { + Calendar calendar = Calendar.getInstance(); + calendar.set(Calendar.YEAR, year); + long startTs = cn.hutool.core.date.DateUtil.beginOfYear(calendar.getTime()).getTime(); + long endTs = cn.hutool.core.date.DateUtil.endOfYear(calendar.getTime()).getTime(); + + List activityList = queryPunchActivityList(startTs, endTs); + if (CollectionUtil.isEmpty(activityList)) { + return Collections.emptyList(); + } + + Map> activityGroupMap = activityList.stream() + .collect(Collectors.groupingBy(this::buildUserPunchCascaderGroupKey, LinkedHashMap::new, Collectors.toList())); + + return activityGroupMap.values().stream().map(groupList -> { + FitnessWalkActivity firstActivity = groupList.get(0); + List childActivityList = Strings.isNotBlank(firstActivity.getRelationActivityId()) + ? queryRelationActivityList(firstActivity.getRelationActivityId(), FitnessWalkMode.PUNCH.getValue()) + : groupList; + Cascader cascader = new Cascader(); + cascader.setValue(buildUserPunchCascaderParentValue(firstActivity)); + cascader.setLabel(Strings.sNull(firstActivity.getMasterActivityName())); + if (childActivityList.size() > 1) { + List children = childActivityList.stream().map(activity -> { + Cascader child = new Cascader(); + child.setValue(activity.get_id()); + child.setLabel(Strings.isBlank(activity.getBranchActivityName()) ? Strings.sNull(activity.getMasterActivityName()) : activity.getBranchActivityName()); + return child; + }).collect(Collectors.toList()); + cascader.setChildren(children); + } + return cascader; + }).collect(Collectors.toList()); + } + + @Override + public List unionRegCheckInNum(String mainActivityId, String subActivityId, String activityId, String unionId) { + List activityIds = resolveActivityIds(mainActivityId, subActivityId, activityId); + if (activityIds.isEmpty()) { + return Collections.emptyList(); + } + List unions = dao().query(Sys_union.class, Cnd.NEW().desc("unioncode")); //查询报名的数据 StringBuffer regCountSql = new StringBuffer(); regCountSql.append("db.collection('register').aggregate()"); regCountSql.append(".match({"); - regCountSql.append("activity_id:'").append(activityId).append("'"); + regCountSql.append(buildActivityMatchSql(activityIds)); if (StrUtil.isNotBlank(unionId)) { regCountSql.append(",unionid:'").append(unionId).append("'"); } @@ -43,9 +119,9 @@ public class FitnessWalkPunchStatisticsServiceImpl extends BaseServiceImpl regCountData = regCountJsonObject.getJSONArray("data").stream().map(v -> { - JSONObject rowData = JSONUtil.parseObj((String) v); - String id = rowData.getStr("_id"); - int count = rowData.getJSONObject("count").getInt("$numberInt"); + JSONObject rowData = JSONObject.parseObject((String) v); + String id = rowData.getString("_id"); + int count = rowData.getJSONObject("count").getIntValue("$numberInt"); return NutMap.NEW().addv("id", id).addv("count", count); }).collect(Collectors.toMap(v -> v.getString("id"), v -> v.getInt("count"))); @@ -53,20 +129,22 @@ public class FitnessWalkPunchStatisticsServiceImpl extends BaseServiceImpl checkInCountData = checkInCountJsonObject.getJSONArray("data").stream().map(v -> { - JSONObject rowData = JSONUtil.parseObj((String) v); - String id = rowData.getStr("unionid"); - int count = rowData.getJSONObject("count").getInt("$numberInt"); + JSONObject rowData = JSONObject.parseObject((String) v); + String id = rowData.getString("unionid"); + int count = parseCloudNumber(rowData.get("count")); return NutMap.NEW().addv("id", id).addv("count", count); }).collect(Collectors.toMap(v -> v.getString("id"), v -> v.getInt("count"))); @@ -88,15 +166,21 @@ public class FitnessWalkPunchStatisticsServiceImpl extends BaseServiceImpl unionRegistrationData(String activityId, String unionId) { + public List unionRegistrationData(String mainActivityId, String subActivityId, String activityId, String unionId) { + List activityIds = resolveActivityIds(mainActivityId, subActivityId, activityId); + if (activityIds.isEmpty()) { + return Collections.emptyList(); + } + StringBuffer sql = new StringBuffer("db.collection('register').aggregate()"); sql.append(".match({"); - sql.append("activity_id:'").append(activityId).append("',unionid:'").append(unionId).append("'"); + sql.append(buildActivityMatchSql(activityIds)).append(",unionid:'").append(unionId).append("'"); sql.append("})"); sql.append(".addFields({register_time_date:$.toDate('$register_time')})"); sql.append(""" .project({ _id:true, + activity_id:true, loginname:true, username:true, unitname:true, @@ -114,17 +198,25 @@ public class FitnessWalkPunchStatisticsServiceImpl extends BaseServiceImpl list = jsonObject.getJSONArray("data").stream().map(v -> JSONUtil.parseObj((String) v)).collect(Collectors.toList()); + List list = jsonObject.getJSONArray("data").stream().map(v -> JSONObject.parseObject((String) v)).collect(Collectors.toList()); + Map activityNameMap = fitnessWalkCommonService.getAllActivity().stream() + .filter(activity -> Strings.isNotBlank(activity.get_id())) + .collect(Collectors.toMap(FitnessWalkActivity::get_id, this::buildActivityName, (left, right) -> left)); + list.forEach(item -> item.put("activityName", activityNameMap.getOrDefault(item.getString("activity_id"), ""))); return list; } @Override - public List unionCheckInData(String activityId, String unionId) { - // 查询打卡的数据,根据分工会来 + public List unionCheckInData(String mainActivityId, String subActivityId, String activityId, String unionId) { + List activityIds = resolveActivityIds(mainActivityId, subActivityId, activityId); + if (activityIds.isEmpty()) { + return Collections.emptyList(); + } + String sql = """ db.collection('sign').aggregate() .match({ - activity_id: '%s', + %s, unionid: '%s' }) .lookup({ @@ -177,6 +269,7 @@ public class FitnessWalkPunchStatisticsServiceImpl extends BaseServiceImpl list = jsonObject.getJSONArray("data").stream().map(v -> { - JSONObject jo = JSONUtil.parseObj((String) v); - if (jo.getInt("sign_size") > jo.getInt("pts_size")) { - jo.put("sign_size", jo.getInt("pts_size")); + JSONObject jo = JSONObject.parseObject((String) v); + if (jo.getIntValue("sign_size") > jo.getIntValue("pts_size")) { + jo.put("sign_size", jo.getIntValue("pts_size")); } return jo; }).collect(Collectors.toList()); + Map activityNameMap = fitnessWalkCommonService.getAllActivity().stream() + .filter(activity -> Strings.isNotBlank(activity.get_id())) + .collect(Collectors.toMap(FitnessWalkActivity::get_id, this::buildActivityName, (left, right) -> left)); + list.forEach(item -> item.put("activityName", activityNameMap.getOrDefault(item.getString("activity_id"), ""))); return list; } @Override - public List exportNotGetGiftVoucherUsers(String activityId) { + public List exportNotGetGiftVoucherUsers(String mainActivityId, String subActivityId, String activityId) { + List activityIds = resolveActivityIds(mainActivityId, subActivityId, activityId); + if (activityIds.isEmpty()) { + return Collections.emptyList(); + } + //查询报名的人员 JSONObject regJsonObject = weAppCloudUtil.request(WeAppCloudUtil.CRUD.AGGREGATE, """ db.collection('register').aggregate([ - { $match: { activity_id: '%s' } } + { $match: { %s } } ]) .sort({ unitname: 1 }) .end() - """.formatted(activityId)); + """.formatted(buildActivityMatchSql(activityIds))); //查询打完卡获得礼品券的人员 JSONObject giftUserJsonObject = weAppCloudUtil.request(WeAppCloudUtil.CRUD.AGGREGATE, """ db.collection('gift_voucher').aggregate() - .match({ activity_id: '%s' }) + .match({ %s }) .group({ _id: '$loginname' }) .project({ _id: 0, loginname: '$_id' }) .end() - """.formatted(activityId)); + """.formatted(buildActivityMatchSql(activityIds))); List giftUserLoginNames = giftUserJsonObject.getJSONArray("data").stream().map(v -> { - JSONObject jo = JSONUtil.parseObj((String) v); - return jo.getStr("loginname"); - }).toList(); + JSONObject jo = JSON.parseObject((String) v); + return jo.getString("loginname"); + }).collect(Collectors.toList()); - List list = regJsonObject.getJSONArray("data").stream() - .map(v -> JSONUtil.parseObj((String) v)) - .filter(v -> !giftUserLoginNames.contains(v.getStr("loginname"))).collect(Collectors.toList()); + List list = regJsonObject.getJSONArray("data").stream().map(v -> JSON.parseObject((String) v)).filter(v -> !giftUserLoginNames.contains(v.getString("loginname"))).collect(Collectors.toList()); + // 基于结果集中的 unitid 二次过滤单位表并回填 unitname,确保导出单位名称准确。 + fillUnitNameByUnitId(list); return list; } @Override - public List exportNotUseGiftVoucherUsers(String activityId) { - //查询未使用礼品券的人员 + public List exportNotUseGiftVoucherUsers(String mainActivityId, String subActivityId, String activityId) { + List activityIds = resolveActivityIds(mainActivityId, subActivityId, activityId); + if (activityIds.isEmpty()) { + return Collections.emptyList(); + } + JSONObject jsonObject = weAppCloudUtil.request(WeAppCloudUtil.CRUD.AGGREGATE, """ db.collection('gift_voucher').aggregate() - .match({ activity_id: '%s', used: false }) + .match({ %s, used: false }) .sort({ unitname: 1 }) .end() - """.formatted(activityId)); + """.formatted(buildActivityMatchSql(activityIds))); + List list = jsonObject.getJSONArray("data").stream().map(v -> JSON.parseObject((String) v)).collect(Collectors.toList()); + // 基于结果集中的 unitid 二次过滤单位表并回填 unitname,确保导出单位名称准确。 + fillUnitNameByUnitId(list); + return list; + } + + /** + * 从结果集提取 unitid,按 unitid 批量查询单位表并回填 unitname。 + * 该方法用于导出接口,避免逐条查询导致性能问题。 + * + * @param list 需要回填 unitname 的结果集 + */ + private void fillUnitNameByUnitId(List list) { + if (CollectionUtil.isEmpty(list)) { + return; + } + Set unitIdSet = list.stream() + .map(v -> v.getString("unitid")) + .filter(StrUtil::isNotBlank) + .collect(Collectors.toSet()); + if (CollectionUtil.isEmpty(unitIdSet)) { + return; + } + List unitQuery = dao().query(Sys_unit.class, Cnd.where("id", "in", unitIdSet)); + Map unitNameMap = unitQuery.stream().collect(Collectors.toMap(Sys_unit::getId, Sys_unit::getName, (a, b) -> a)); + for (JSONObject obj : list) { + String unitName = unitNameMap.get(obj.getString("unitid")); + if (StrUtil.isNotBlank(unitName)) { + obj.put("unitname", unitName); + } + } + + } + + /** + * 以人员维度汇总打卡情况。 + * 主活动下未选择分活动时,会把该关联项目下所有活动按人员汇总后返回。 + * @param mainActivityId 主活动ID或relation:关联项目ID + * @param subActivityId 分活动ID,空值表示汇总主活动 + * @param activityId 健步走云端活动ID + * @param unionId 分工会ID,空值表示不追加组织筛选 + * @param searchKeyword 姓名或工号查询内容 + * @param qualifiedStatus qualified表示已达标,unqualified表示未达标,all表示全部 + * @param pageNumber 页码,从1开始 + * @param pageSize 每页记录数 + */ + @Override + public Pagination userPunchPageData(String mainActivityId, String subActivityId, String activityId, String unionId, + String searchKeyword, String qualifiedStatus, int pageNumber, int pageSize) { + pageNumber = pageNumber < 1 ? 1 : pageNumber; + pageSize = pageSize < 1 ? 10 : pageSize; + List displayActivityIds = resolveActivityIds(mainActivityId, subActivityId, activityId); + if (displayActivityIds.isEmpty()) { + return new Pagination(pageNumber, pageSize, 0, Collections.emptyList()); + } + + QualifiedRuleContext qualifiedRuleContext = resolveQualifiedRuleContext(mainActivityId, subActivityId, activityId); + if (needQualifiedFilter(qualifiedStatus, qualifiedRuleContext)) { + List allLoginNames = queryUserLoginNames(displayActivityIds, unionId, searchKeyword, null, null); + List allUserSummaryList = buildUserPunchSummaryList(displayActivityIds, unionId, allLoginNames, qualifiedRuleContext); + List filteredList = filterByQualifiedStatus(allUserSummaryList, qualifiedRuleContext.getStandardActivityNum(), qualifiedStatus); + return new Pagination(pageNumber, pageSize, filteredList.size(), pageSubList(filteredList, pageNumber, pageSize)); + } + + int totalCount = countUserLoginNames(displayActivityIds, unionId, searchKeyword); + List pageLoginNames = queryUserLoginNames(displayActivityIds, unionId, searchKeyword, pageNumber, pageSize); + List pageList = buildUserPunchSummaryList(displayActivityIds, unionId, pageLoginNames, qualifiedRuleContext); + return new Pagination(pageNumber, pageSize, totalCount, pageList); + } + + @Override + public List userPunchExportData(String mainActivityId, String subActivityId, String activityId, String unionId, String searchKeyword, String qualifiedStatus) { + List displayActivityIds = resolveActivityIds(mainActivityId, subActivityId, activityId); + if (displayActivityIds.isEmpty()) { + return Collections.emptyList(); + } + QualifiedRuleContext qualifiedRuleContext = resolveQualifiedRuleContext(mainActivityId, subActivityId, activityId); + List allLoginNames = queryUserLoginNames(displayActivityIds, unionId, searchKeyword, null, null); + List userSummaryList = buildUserPunchSummaryList(displayActivityIds, unionId, allLoginNames, qualifiedRuleContext); + return filterByQualifiedStatus(userSummaryList, qualifiedRuleContext.getStandardActivityNum(), qualifiedStatus); + } + + /** + * 按人员维度查询登录名。 + * searchKeyword 支持按姓名或工号模糊查询;列表查询传入 pageNumber/pageSize 时只取当前页人员,导出传空时取全部人员。 + */ + private List queryUserLoginNames(List activityIds, String unionId, String searchKeyword, Integer pageNumber, Integer pageSize) { + StringBuilder sql = new StringBuilder("db.collection('register').aggregate()"); + sql.append(".match({"); + sql.append(buildRegisterMatchSql(activityIds, unionId, null, searchKeyword)); + sql.append("})"); + sql.append(""" + .group({ + _id: '$loginname', + loginname: { $first: '$loginname' }, + unionname: { $first: '$unionname' }, + unitname: { $first: '$unitname' } + }) + .sort({ unionname: 1, unitname: 1, loginname: 1 }) + """); + if (pageNumber != null && pageSize != null) { + int skip = (pageNumber <= 1 ? 0 : pageNumber - 1) * pageSize; + sql.append(".skip(").append(skip).append(")"); + sql.append(".limit(").append(pageSize).append(")"); + } + sql.append(".project({_id: 0, loginname: '$loginname'}).end()"); + + JSONObject jsonObject = weAppCloudUtil.request(WeAppCloudUtil.CRUD.AGGREGATE, sql.toString()); + if (jsonObject.getJSONArray("data") == null || jsonObject.getJSONArray("data").isEmpty()) { + return Collections.emptyList(); + } return jsonObject.getJSONArray("data").stream() - .map(v -> JSONUtil.parseObj((String) v)).collect(Collectors.toList()); + .map(item -> JSON.parseObject((String) item).getString("loginname")) + .filter(StrUtil::isNotBlank) + .collect(Collectors.toList()); + } + + /** + * 统计当前筛选条件下的人员总数。 + * 这里按 loginname 去重,和页面按人员维度展示的口径保持一致。 + */ + private int countUserLoginNames(List activityIds, String unionId, String searchKeyword) { + StringBuilder sql = new StringBuilder("db.collection('register').aggregate()"); + sql.append(".match({"); + sql.append(buildRegisterMatchSql(activityIds, unionId, null, searchKeyword)); + sql.append("})"); + sql.append(""" + .group({ _id: '$loginname' }) + .group({ _id: 0, total: $.sum(1) }) + .project({ _id: 0, total: '$total' }) + .end() + """); + + JSONObject jsonObject = weAppCloudUtil.request(WeAppCloudUtil.CRUD.AGGREGATE, sql.toString()); + if (jsonObject.getJSONArray("data") == null || jsonObject.getJSONArray("data").isEmpty()) { + return 0; + } + JSONObject rowData = JSON.parseObject((String) jsonObject.getJSONArray("data").get(0)); + return parseCloudNumber(rowData.get("total")); + } + + /** + * 按人员批次构建汇总结果。 + * 列表通常只传当前页人员;导出传全部人员后按固定批次拆开,避免单次云数据库请求过大。 + */ + private List buildUserPunchSummaryList(List displayActivityIds, + String unionId, + List loginNames, + QualifiedRuleContext qualifiedRuleContext) { + if (CollectionUtil.isEmpty(loginNames)) { + return Collections.emptyList(); + } + + Map> displayUserActivityMap = new LinkedHashMap<>(); + Map qualifiedActivityNumMap = new LinkedHashMap<>(); + for (int start = 0; start < loginNames.size(); start += EXPORT_QUERY_BATCH_SIZE) { + List batchLoginNames = loginNames.subList(start, Math.min(start + EXPORT_QUERY_BATCH_SIZE, loginNames.size())); + Map> batchDisplayMap = queryUserActivityMap(displayActivityIds, unionId, batchLoginNames); + displayUserActivityMap.putAll(batchDisplayMap); + qualifiedActivityNumMap.putAll(buildQualifiedActivityNumMap(batchDisplayMap, displayActivityIds, unionId, qualifiedRuleContext, batchLoginNames)); + } + + return loginNames.stream() + .map(displayUserActivityMap::get) + .filter(CollectionUtil::isNotEmpty) + .map(activityRows -> buildUserPunchSummary(activityRows, qualifiedActivityNumMap)) + .collect(Collectors.toList()); + } + + /** + * 查询页面当前需要展示的“人员-活动”明细。 + * 这里不再使用 register 逐条 lookup sign 的大聚合,而是拆成报名行、活动点位数、打卡点位数三段批量查询。 + */ + private Map> queryUserActivityMap(List activityIds, String unionId, List loginNames) { + List registerRows = queryRegisterActivityRows(activityIds, unionId, loginNames); + if (CollectionUtil.isEmpty(registerRows)) { + return Collections.emptyMap(); + } + + Map activityPtsSizeMap = queryActivityPtsSizeMap(activityIds); + Map signSizeMap = querySignSizeMap(activityIds, loginNames); + + return registerRows.stream().map(rowData -> { + String activityId = rowData.getString("activity_id"); + String loginname = rowData.getString("loginname"); + int ptsSize = activityPtsSizeMap.getOrDefault(activityId, 0); + int signSize = signSizeMap.getOrDefault(buildUserActivityKey(activityId, loginname), 0); + if (signSize > ptsSize && ptsSize > 0) { + signSize = ptsSize; + } + return NutMap.NEW() + .addv("activity_id", activityId) + .addv("userId", rowData.getString("userId")) + .addv("username", rowData.getString("username")) + .addv("loginname", loginname) + .addv("unionname", rowData.getString("unionname")) + .addv("unitname", rowData.getString("unitname")) + .addv("pts_size", ptsSize) + .addv("sign_size", signSize) + .addv("activityQualified", ptsSize > 0 && signSize >= ptsSize); + }).collect(Collectors.groupingBy(row -> row.getString("loginname"), LinkedHashMap::new, Collectors.toList())); + } + + /** + * 查询报名表中的人员-活动基础行。 + * 参数说明:activityIds 为活动ID范围,unionId 为分工会ID,loginNames 为当前批次人员工号;返回每人每活动一行。 + */ + private List queryRegisterActivityRows(List activityIds, String unionId, List loginNames) { + StringBuilder sql = new StringBuilder("db.collection('register').aggregate()"); + sql.append(".match({"); + sql.append(buildRegisterMatchSql(activityIds, unionId, loginNames, null)); + sql.append("})"); + sql.append(""" + .group({ + _id: { + activity_id: '$activity_id', + loginname: '$loginname' + }, + userId: { $first: '$userid' }, + loginname: { $first: '$loginname' }, + username: { $first: '$username' }, + unionname: { $first: '$unionname' }, + unitname: { $first: '$unitname' } + }) + .project({ + _id: 0, + activity_id: '$_id.activity_id', + userId: 1, + loginname: 1, + username: 1, + unionname: 1, + unitname: 1 + }) + .sort({ unionname: 1, unitname: 1, loginname: 1 }) + .end() + """); + + JSONObject jsonObject = weAppCloudUtil.request(WeAppCloudUtil.CRUD.AGGREGATE, sql.toString()); + if (jsonObject.getJSONArray("data") == null || jsonObject.getJSONArray("data").isEmpty()) { + return Collections.emptyList(); + } + return jsonObject.getJSONArray("data").stream() + .map(item -> JSON.parseObject((String) item)) + .collect(Collectors.toList()); + } + + /** + * 查询活动点位数量。 + * 返回值 Map 的 key 为 activityId,value 为该活动配置的点位总数。 + */ + private Map queryActivityPtsSizeMap(List activityIds) { + if (CollectionUtil.isEmpty(activityIds)) { + return Collections.emptyMap(); + } + + StringBuilder sql = new StringBuilder("db.collection('activity').where({"); + sql.append(buildFieldMatchSql("_id", activityIds)); + sql.append("}).field({_id:true,pts:true}).limit(1000).get()"); + + JSONObject jsonObject = weAppCloudUtil.request(WeAppCloudUtil.CRUD.QUERY, sql.toString()); + if (jsonObject.getJSONArray("data") == null || jsonObject.getJSONArray("data").isEmpty()) { + return Collections.emptyMap(); + } + return jsonObject.getJSONArray("data").stream() + .map(item -> JSON.parseObject((String) item, FitnessWalkActivity.class)) + .filter(activity -> Strings.isNotBlank(activity.get_id())) + .collect(Collectors.toMap(FitnessWalkActivity::get_id, activity -> activity.getPts() == null ? 0 : activity.getPts().size(), (left, right) -> left)); + } + + /** + * 查询人员在活动中的已打卡点位数量。 + * 这里按 activity_id + loginname + pts_code 去重,避免重复打卡把完成情况算高。 + */ + private Map querySignSizeMap(List activityIds, List loginNames) { + if (CollectionUtil.isEmpty(activityIds) || CollectionUtil.isEmpty(loginNames)) { + return Collections.emptyMap(); + } + + StringBuilder sql = new StringBuilder("db.collection('sign').aggregate()"); + sql.append(".match({"); + sql.append(buildActivityMatchSql(activityIds)); + sql.append(",loginname:_.in(").append(JSON.toJSONString(loginNames)).append(")"); + sql.append("})"); + sql.append(""" + .group({ + _id: { + activity_id: '$activity_id', + loginname: '$loginname' + }, + signedPtsCodeSet: $.addToSet('$pts_code') + }) + .project({ + _id: 0, + activity_id: '$_id.activity_id', + loginname: '$_id.loginname', + signedPtsNum: $.size('$signedPtsCodeSet') + }) + .end() + """); + + JSONObject jsonObject = weAppCloudUtil.request(WeAppCloudUtil.CRUD.AGGREGATE, sql.toString()); + if (jsonObject.getJSONArray("data") == null || jsonObject.getJSONArray("data").isEmpty()) { + return Collections.emptyMap(); + } + return jsonObject.getJSONArray("data").stream() + .map(item -> JSON.parseObject((String) item)) + .collect(Collectors.toMap( + row -> buildUserActivityKey(row.getString("activity_id"), row.getString("loginname")), + row -> parseCloudNumber(row.get("signedPtsNum")), + (left, right) -> left + )); + } + + /** + * 把“人员-活动”明细汇总成页面列表需要的“人员”维度数据。 + * 达标活动数统一使用关联活动规则口径,避免和当前展示活动范围耦合。 + */ + private NutMap buildUserPunchSummary(List activityRows, Map qualifiedActivityNumMap) { + NutMap firstRow = activityRows.get(0); + int totalPtsSize = 0; + int totalSignSize = 0; + for (NutMap activityRow : activityRows) { + int ptsSize = activityRow.getInt("pts_size"); + int signSize = activityRow.getInt("sign_size"); + totalPtsSize += ptsSize; + totalSignSize += signSize; + } + int qualifiedActivityNum = qualifiedActivityNumMap.getOrDefault(firstRow.getString("loginname"), 0); + return NutMap.NEW() + .addv("userId", firstRow.getString("userId")) + .addv("username", firstRow.getString("username")) + .addv("loginname", firstRow.getString("loginname")) + .addv("unionname", firstRow.getString("unionname")) + .addv("unitname", firstRow.getString("unitname")) + .addv("pts_size", totalPtsSize) + .addv("sign_size", totalSignSize) + .addv("qualifiedActivityNum", qualifiedActivityNum) + .addv("isAllSigned", totalPtsSize > 0 && totalSignSize >= totalPtsSize ? "是" : "否"); + } + + /** + * 统计每个人在关联活动规则下已完成的活动数。 + * 如果当前查询活动没有配置关联项目,则退回当前展示活动范围的完成数。 + */ + private Map buildQualifiedActivityNumMap(Map> displayUserActivityMap, + List displayActivityIds, + String unionId, + QualifiedRuleContext qualifiedRuleContext, + List loginNames) { + if (displayUserActivityMap.isEmpty()) { + return Collections.emptyMap(); + } + + Map> sourceActivityMap; + if (qualifiedRuleContext == null + || qualifiedRuleContext.getQualifiedActivityIds().isEmpty() + || sameActivityIds(displayActivityIds, qualifiedRuleContext.getQualifiedActivityIds())) { + sourceActivityMap = displayUserActivityMap; + } else { + sourceActivityMap = queryUserActivityMap(qualifiedRuleContext.getQualifiedActivityIds(), unionId, loginNames); + } + + return sourceActivityMap.entrySet().stream().collect(Collectors.toMap( + Map.Entry::getKey, + entry -> (int) entry.getValue().stream().filter(row -> row.getBoolean("activityQualified")).count(), + (left, right) -> left, + LinkedHashMap::new + )); + } + + /** + * 判断本次是否需要先计算全量人员后再做达标过滤。 + * 达标筛选会影响总数,不能先分页再过滤,否则页面总数和当前页数据都会不准。 + */ + private boolean needQualifiedFilter(String qualifiedStatus, QualifiedRuleContext qualifiedRuleContext) { + if (!"qualified".equals(qualifiedStatus) && !"unqualified".equals(qualifiedStatus)) { + return false; + } + return qualifiedRuleContext != null + && qualifiedRuleContext.getStandardActivityNum() != null + && qualifiedRuleContext.getStandardActivityNum() > 0; + } + + /** + * 从完整结果中截取页面当前页。 + * 仅用于达标状态筛选场景,因为该场景必须先得到筛选后的总数再分页。 + */ + private List pageSubList(List list, int pageNumber, int pageSize) { + if (CollectionUtil.isEmpty(list)) { + return Collections.emptyList(); + } + int fromIndex = Math.max(0, (pageNumber - 1) * pageSize); + if (fromIndex >= list.size()) { + return Collections.emptyList(); + } + int toIndex = Math.min(fromIndex + pageSize, list.size()); + return list.subList(fromIndex, toIndex); + } + + private boolean sameActivityIds(List left, List right) { + Set leftSet = left == null ? Collections.emptySet() : new HashSet<>(left); + Set rightSet = right == null ? Collections.emptySet() : new HashSet<>(right); + return leftSet.equals(rightSet); + } + + /** + * 达标筛选按关联项目配置生效。 + * 当页面选择“全部”时直接返回原列表;若当前活动没有解析出关联达标配置,则保底返回原列表,避免把列表直接筛空。 + */ + private List filterByQualifiedStatus(List userSummaryList, Integer standardActivityNum, String qualifiedStatus) { + // “全部”是用户主动不筛选,直接返回原列表。 + if (Strings.isBlank(qualifiedStatus) || "all".equals(qualifiedStatus)) { + return userSummaryList; + } + // 页面明确选择了“已达标/未达标”,但当前活动没有关联达标配置时,先保底返回原列表,避免页面完全查不出数据。 + if (standardActivityNum == null || standardActivityNum <= 0) { + return userSummaryList; + } + if ("qualified".equals(qualifiedStatus)) { + return userSummaryList.stream() + .filter(item -> item.getInt("qualifiedActivityNum") >= standardActivityNum) + .collect(Collectors.toList()); + } + if ("unqualified".equals(qualifiedStatus)) { + return userSummaryList.stream() + .filter(item -> item.getInt("qualifiedActivityNum") < standardActivityNum) + .collect(Collectors.toList()); + } + return userSummaryList; + } + + /** + * 查询人员打卡统计页活动列表。 + * 该方法只服务人员打卡统计页,直接从云数据库读取,避免复用公共活动级联影响其他页面。 + */ + private List queryPunchActivityList(long startTs, long endTs) { + StringBuilder sql = new StringBuilder("db.collection('activity').where({activityModel:'"); + sql.append(FitnessWalkMode.PUNCH.getValue()) + .append("',startTime:_.gte(").append(startTs).append(").and(_.lte(").append(endTs).append("))") + .append("}).field({") + .append("_id:true,") + .append("masterActivityName:true,") + .append("branchActivityName:true,") + .append("relationActivityId:true,") + .append("activityModel:true,") + .append("startTime:true,") + .append("unionId:true") + .append("}).orderBy('startTime','desc').limit(1000).get()"); + JSONObject jsonObject = weAppCloudUtil.request(WeAppCloudUtil.CRUD.QUERY, sql.toString()); + if (jsonObject.getJSONArray("data") == null || jsonObject.getJSONArray("data").isEmpty()) { + return Collections.emptyList(); + } + List activityList = jsonObject.getJSONArray("data").stream() + .map(item -> JSON.parseObject((String) item, FitnessWalkActivity.class)) + .collect(Collectors.toList()); + return activityList; + } + + /** + * 查询关联项目下全部打卡活动。 + * 人员打卡统计页的子活动列表和主活动汇总都应使用完整关联范围,不能只取年份筛选后的活动。 + */ + private List queryRelationActivityList(String relationActivityId, String activityModel) { + if (Strings.isBlank(relationActivityId)) { + return Collections.emptyList(); + } + StringBuilder sql = new StringBuilder("db.collection('activity').where({relationActivityId:'"); + sql.append(relationActivityId) + .append("',activityModel:'").append(activityModel).append("'") + .append("}).field({") + .append("_id:true,") + .append("masterActivityName:true,") + .append("branchActivityName:true,") + .append("relationActivityId:true,") + .append("activityModel:true,") + .append("startTime:true,") + .append("unionId:true") + .append("}).orderBy('startTime','desc').limit(1000).get()"); + JSONObject jsonObject = weAppCloudUtil.request(WeAppCloudUtil.CRUD.QUERY, sql.toString()); + if (jsonObject.getJSONArray("data") == null || jsonObject.getJSONArray("data").isEmpty()) { + return Collections.emptyList(); + } + List activityList = jsonObject.getJSONArray("data").stream() + .map(item -> JSON.parseObject((String) item, FitnessWalkActivity.class)) + .collect(Collectors.toList()); + return activityList; + } + + /** + * 构建人员打卡统计页主活动分组键。 + * 有关联项目时按关联项目聚合;历史活动没有关联项目时退回按自身活动ID展示。 + */ + private String buildUserPunchCascaderGroupKey(FitnessWalkActivity activity) { + if (Strings.isNotBlank(activity.getRelationActivityId())) { + return RELATION_ACTIVITY_PREFIX + activity.getRelationActivityId(); + } + return Strings.sNull(activity.get_id()); + } + + /** + * 构建人员打卡统计页主活动父级值。 + * relation: 前缀用于后端区分“关联项目ID”和普通活动ID。 + */ + private String buildUserPunchCascaderParentValue(FitnessWalkActivity activity) { + if (Strings.isNotBlank(activity.getRelationActivityId())) { + return RELATION_ACTIVITY_PREFIX + activity.getRelationActivityId(); + } + return activity.get_id(); + } + + private boolean isRelationActivityValue(String value) { + return Strings.sNull(value).startsWith(RELATION_ACTIVITY_PREFIX); + } + + private String getRelationActivityId(String value) { + return Strings.sNull(value).substring(RELATION_ACTIVITY_PREFIX.length()); + } + + /** + * 根据关联项目ID查询活动ID。 + * 人员打卡统计页主活动父级直接传 relation:关联项目ID 时走这里,不依赖公共活动缓存。 + */ + private List queryActivityIdsByRelationId(String relationActivityId, String activityModel) { + if (Strings.isBlank(relationActivityId)) { + return Collections.emptyList(); + } + StringBuilder sql = new StringBuilder("db.collection('activity').where({relationActivityId:'"); + sql.append(relationActivityId) + .append("',activityModel:'").append(activityModel).append("'") + .append("}).field({_id:true}).limit(1000).get()"); + JSONObject jsonObject = weAppCloudUtil.request(WeAppCloudUtil.CRUD.QUERY, sql.toString()); + if (jsonObject.getJSONArray("data") == null || jsonObject.getJSONArray("data").isEmpty()) { + return Collections.emptyList(); + } + return jsonObject.getJSONArray("data").stream() + .map(item -> JSON.parseObject((String) item).getString("_id")) + .filter(Strings::isNotBlank) + .distinct() + .collect(Collectors.toList()); + } + + /** + * 根据关联项目ID构造达标规则上下文。 + * 父级传 relation:关联项目ID 时无需先取某个最新活动,直接按关联项目计算达标范围。 + */ + private QualifiedRuleContext buildQualifiedRuleContextByRelationId(String relationActivityId, String activityModel) { + if (Strings.isBlank(relationActivityId)) { + return QualifiedRuleContext.empty(); + } + FitnessWalkActivityRelationProject relationProject = dao().fetch( + FitnessWalkActivityRelationProject.class, + Cnd.where("id", "=", relationActivityId) + ); + if (relationProject == null || relationProject.getStandardActivityNum() == null || relationProject.getStandardActivityNum() <= 0) { + return QualifiedRuleContext.empty(); + } + List qualifiedActivityIds = queryActivityIdsByRelationId(relationActivityId, activityModel); + return new QualifiedRuleContext(relationActivityId, relationProject.getStandardActivityNum(), qualifiedActivityIds); + } + + /** + * 解析当前查询的关联达标规则。 + * 筛选“已达标/未达标”时统一以关联项目下全部同模式活动作为判断范围,而不是当前列表的展示范围。 + */ + private QualifiedRuleContext resolveQualifiedRuleContext(String mainActivityId, String subActivityId, String activityId) { + String selectedActivityId = resolveSelectedActivityId(mainActivityId, subActivityId, activityId); + if (Strings.isBlank(selectedActivityId)) { + return QualifiedRuleContext.empty(); + } + + if (isRelationActivityValue(selectedActivityId)) { + return buildQualifiedRuleContextByRelationId(getRelationActivityId(selectedActivityId), FitnessWalkMode.PUNCH.getValue()); + } + + FitnessWalkActivity selectedActivity = fitnessWalkCommonService.getActivity(selectedActivityId); + if (selectedActivity == null || Strings.isBlank(selectedActivity.getRelationActivityId())) { + return QualifiedRuleContext.empty(); + } + + FitnessWalkActivityRelationProject relationProject = dao().fetch( + FitnessWalkActivityRelationProject.class, + Cnd.where("id", "=", selectedActivity.getRelationActivityId()) + ); + if (relationProject == null || relationProject.getStandardActivityNum() == null || relationProject.getStandardActivityNum() <= 0) { + return QualifiedRuleContext.empty(); + } + + List qualifiedActivityIds = fitnessWalkCommonService.getAllActivity().stream() + .filter(activity -> Strings.isNotBlank(activity.get_id())) + .filter(activity -> Strings.sNull(activity.getRelationActivityId()).equals(selectedActivity.getRelationActivityId())) + .filter(activity -> Strings.sNull(activity.getActivityModel()).equals(selectedActivity.getActivityModel())) + .map(FitnessWalkActivity::get_id) + .distinct() + .collect(Collectors.toList()); + if (qualifiedActivityIds.isEmpty()) { + qualifiedActivityIds = List.of(selectedActivityId); + } + return new QualifiedRuleContext(selectedActivity.getRelationActivityId(), relationProject.getStandardActivityNum(), qualifiedActivityIds); + } + + /** + * 统一解析当前页面真正选中的活动ID。 + * 分活动优先,其次主活动,最后兼容旧页面传入的 activityId。 + */ + private String resolveSelectedActivityId(String mainActivityId, String subActivityId, String activityId) { + if (Strings.isNotBlank(subActivityId)) { + return subActivityId; + } + if (Strings.isNotBlank(mainActivityId)) { + return mainActivityId; + } + return activityId; + } + + /** + * 按页面当前主活动/分活动选择,统一解析本次需要查询的活动ID集合。 + * 规则如下: + * 1. 选择了分活动时,只查询当前分活动; + * 2. 只选择主活动时,先读取主活动对应的 relationActivityId,再查询该关联下全部同模式活动; + * 3. 如果主活动未配置关联项目,则退回只查询当前主活动; + * 4. 最后再兼容旧页面仅传 activityId 的场景。 + */ + private List resolveActivityIds(String mainActivityId, String subActivityId, String activityId) { + if (Strings.isNotBlank(subActivityId)) { + return List.of(subActivityId); + } + + if (Strings.isNotBlank(mainActivityId)) { + if (isRelationActivityValue(mainActivityId)) { + List relationActivityIds = queryActivityIdsByRelationId(getRelationActivityId(mainActivityId), FitnessWalkMode.PUNCH.getValue()); + if (!relationActivityIds.isEmpty()) { + return relationActivityIds; + } + return Collections.emptyList(); + } + + FitnessWalkActivity mainActivity = fitnessWalkCommonService.getActivity(mainActivityId); + if (mainActivity == null) { + return List.of(mainActivityId); + } + + if (Strings.isBlank(mainActivity.getRelationActivityId())) { + return List.of(mainActivityId); + } + + List relationActivityIds = fitnessWalkCommonService.getAllActivity().stream() + .filter(activity -> Strings.isNotBlank(activity.get_id())) + .filter(activity -> Strings.sNull(activity.getRelationActivityId()).equals(mainActivity.getRelationActivityId())) + .filter(activity -> Strings.sNull(activity.getActivityModel()).equals(mainActivity.getActivityModel())) + .map(FitnessWalkActivity::get_id) + .collect(Collectors.toList()); + if (!relationActivityIds.isEmpty()) { + return relationActivityIds; + } + return List.of(mainActivityId); + } + + if (Strings.isNotBlank(activityId)) { + return List.of(activityId); + } + return Collections.emptyList(); + } + + /** + * 统一拼装 activity_id 查询条件。 + * 单个活动时仍走等值查询,多个活动时改为 _.in([...]),方便复用到不同集合的聚合语句里。 + */ + private String buildActivityMatchSql(List activityIds) { + return buildFieldMatchSql("activity_id", activityIds); + } + + /** + * 统一拼装集合字段的等值或 in 查询条件。 + * + * @param fieldName 字段名,例如 activity_id、_id + * @param ids 字段取值集合,单个值走等值,多个值走 _.in + * @return 云数据库 where/match 片段 + */ + private String buildFieldMatchSql(String fieldName, List ids) { + List validActivityIds = ids == null ? Collections.emptyList() : ids.stream() + .filter(Strings::isNotBlank) + .distinct() + .collect(Collectors.toList()); + if (validActivityIds.isEmpty()) { + return fieldName + ":''"; + } + if (validActivityIds.size() == 1) { + return fieldName + ":'" + validActivityIds.get(0) + "'"; + } + return fieldName + ":_.in(" + JSON.toJSONString(validActivityIds) + ")"; + } + + /** + * 统一构建报名表查询条件。 + * activityIds 表示活动范围,unionId 表示分工会过滤,loginNames 表示当前查询的人员批次,searchKeyword 表示姓名/工号关键字。 + */ + private String buildRegisterMatchSql(List activityIds, String unionId, List loginNames, String searchKeyword) { + StringBuilder sql = new StringBuilder(buildActivityMatchSql(activityIds)); + if (StrUtil.isNotBlank(unionId)) { + sql.append(",unionid:'").append(unionId).append("'"); + } + if (StrUtil.isNotBlank(searchKeyword)) { + String keyword = escapeCloudRegexKeyword(searchKeyword); + sql.append(",$or:["); + sql.append("{loginname:{$regex:'").append(keyword).append("',$options:'i'}},"); + sql.append("{username:{$regex:'").append(keyword).append("',$options:'i'}}"); + sql.append("]"); + } + if (CollectionUtil.isNotEmpty(loginNames)) { + sql.append(",loginname:_.in(").append(JSON.toJSONString(loginNames)).append(")"); + } + return sql.toString(); + } + + /** + * 云数据库使用 $regex 做姓名/工号模糊查询,先转义用户输入,避免特殊正则字符影响查询范围。 + */ + private String escapeCloudRegexKeyword(String searchKeyword) { + String keyword = Strings.sNull(searchKeyword).trim(); + StringBuilder escaped = new StringBuilder(); + for (int i = 0; i < keyword.length(); i++) { + char ch = keyword.charAt(i); + if (ch == '\\') { + escaped.append("\\\\\\\\"); + } else if (".^$|?*+()[]{}".indexOf(ch) >= 0) { + escaped.append("\\\\"); + } else if (ch == '\'') { + escaped.append("\\"); + } + escaped.append(ch); + } + return escaped.toString(); + } + + private String buildUserActivityKey(String activityId, String loginname) { + return Strings.sNull(activityId) + "||" + Strings.sNull(loginname); + } + + /** + * 云函数聚合返回的数字字段可能是普通数字,也可能是 {$numberInt:xx} 包装结构。 + * 这里统一做兼容,避免不同聚合阶段取值时报错。 + */ + private int parseCloudNumber(Object value) { + if (value == null) { + return 0; + } + if (value instanceof Number) { + return ((Number) value).intValue(); + } + if (value instanceof JSONObject) { + JSONObject jsonObject = (JSONObject) value; + if (jsonObject.containsKey("$numberInt")) { + return jsonObject.getIntValue("$numberInt"); + } + if (jsonObject.containsKey("$numberLong")) { + return jsonObject.getIntValue("$numberLong"); + } + } + return Integer.parseInt(String.valueOf(value)); + } + + /** + * 统一生成活动展示名称。 + * 没有分活动名称时仅显示主活动名称;有分活动名称时按“主活动(分活动)”展示。 + */ + private String buildActivityName(FitnessWalkActivity activity) { + if (activity == null) { + return ""; + } + String masterActivityName = Strings.sNull(activity.getMasterActivityName()).trim(); + String branchActivityName = Strings.sNull(activity.getBranchActivityName()).trim(); + if (Strings.isBlank(branchActivityName)) { + return masterActivityName; + } + return masterActivityName + "(" + branchActivityName + ")"; + } + + /** + * 统计页关联达标规则上下文。 + * 单独封装后,列表展示范围和达标判断范围可以清晰分离。 + */ + private static class QualifiedRuleContext { + private final String relationActivityId; + private final Integer standardActivityNum; + private final List qualifiedActivityIds; + + private QualifiedRuleContext(String relationActivityId, Integer standardActivityNum, List qualifiedActivityIds) { + this.relationActivityId = relationActivityId; + this.standardActivityNum = standardActivityNum; + this.qualifiedActivityIds = qualifiedActivityIds == null ? Collections.emptyList() : qualifiedActivityIds; + } + + private static QualifiedRuleContext empty() { + return new QualifiedRuleContext(null, null, Collections.emptyList()); + } + + public String getRelationActivityId() { + return relationActivityId; + } + + public Integer getStandardActivityNum() { + return standardActivityNum; + } + + public List getQualifiedActivityIds() { + return qualifiedActivityIds; + } + } + + /** + * 导出所选主分活动的分工会报名与打卡人数。 + * + * @param response HTTP响应,用于写入XSSF格式Excel文件流 + * @param mainActivityId 主活动ID或relation:关联项目ID + * @param subActivityId 分活动ID,空值表示主活动汇总 + * @param activityId 健步走云端活动ID + * 无返回对象,通过response写出Excel文件流。 + */ + @Override + @Ok("void") + public void exportUnionRegCheckInNum(HttpServletResponse response, String mainActivityId, String subActivityId, String activityId) { + List list = this.unionRegCheckInNum(mainActivityId, subActivityId, activityId, null); + List entities = new ArrayList<>(); + entities.add(new ExcelExportEntity("分工会", "unionname", 20)); + entities.add(new ExcelExportEntity("报名人数", "regCount", 20)); + entities.add(new ExcelExportEntity("打卡人数", "checkInCount", 20)); + + try { + ExportParams exportParams = new ExportParams(); + exportParams.setType(ExcelType.XSSF); + Workbook workbook = ExcelExportUtil.exportExcel(exportParams, entities, list); + CommonDownloadUtil.download("各分工会统计人数.xlsx", workbook, response); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + /** + * 导出所选活动和分工会的报名人员明细。 + * + * @param response HTTP响应,用于写入XSSF格式Excel文件流 + * @param mainActivityId 主活动ID或relation:关联项目ID + * @param subActivityId 分活动ID,空值表示主活动汇总 + * @param activityId 健步走云端活动ID + * @param unionId 分工会ID,空值表示不追加筛选,已有数据权限仍生效 + * 无返回对象,通过response写出Excel文件流。 + */ + @Override + @Ok("void") + public void exportRegUsers(HttpServletResponse response, String mainActivityId, String subActivityId, String activityId, String unionId) { + Sys_union union = dao().fetch(Sys_union.class, unionId); + String unionName = Optional.ofNullable(union).map(Sys_union::getName).orElse(null); + + List list = this.unionRegistrationData(mainActivityId, subActivityId, activityId, unionId); + List entities = new ArrayList<>(); + entities.add(new ExcelExportEntity("活动名称", "activityName", 30)); + entities.add(new ExcelExportEntity("工号", "loginname", 20)); + entities.add(new ExcelExportEntity("姓名", "username", 20)); + entities.add(new ExcelExportEntity("单位", "unitname", 20)); +// entities.add(new ExcelExportEntity("手机号", "mobile", 20)); + entities.add(new ExcelExportEntity("报名时间", "register_time_str", 20)); + + try { + ExportParams exportParams = new ExportParams(); + exportParams.setType(ExcelType.XSSF); + Workbook workbook = ExcelExportUtil.exportExcel(exportParams, entities, list); + CommonDownloadUtil.download(unionName + "分工会报名人员.xlsx", workbook, response); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + /** + * 导出所选活动和分工会的打卡点位完成明细。 + * + * @param response HTTP响应,用于写入XSSF格式Excel文件流 + * @param mainActivityId 主活动ID或relation:关联项目ID + * @param subActivityId 分活动ID,空值表示主活动汇总 + * @param activityId 健步走云端活动ID + * @param unionId 分工会ID,空值表示不追加筛选,已有数据权限仍生效 + * 无返回对象,通过response写出Excel文件流。 + */ + @Override + @Ok("void") + public void exportCheckInUsers(HttpServletResponse response, String mainActivityId, String subActivityId, String activityId, String unionId) { + Sys_union union = dao().fetch(Sys_union.class, unionId); + String unionName = Optional.ofNullable(union).map(Sys_union::getName).orElse(null); + + List list = this.unionCheckInData(mainActivityId, subActivityId, activityId, unionId); + + for (JSONObject jsonObject : list) { + Boolean used = jsonObject.getBoolean("used"); + if (used == null) { + jsonObject.put("used", "未取得"); + } else if (used) { + jsonObject.put("used", "已领取"); + } else { + jsonObject.put("used", "未领取"); + } + } + + List entities = new ArrayList<>(); + entities.add(new ExcelExportEntity("活动名称", "activityName", 30)); + entities.add(new ExcelExportEntity("工号", "loginname", 20)); + entities.add(new ExcelExportEntity("姓名", "username", 20)); + entities.add(new ExcelExportEntity("单位", "unitname", 20)); +// entities.add(new ExcelExportEntity("手机号", "mobile", 20)); + entities.add(new ExcelExportEntity("点位总数", "pts_size", 20)); + entities.add(new ExcelExportEntity("已打卡点位数", "sign_size", 20)); +// entities.add(new ExcelExportEntity("礼品券", "used", 20)); + + try { + ExportParams exportParams = new ExportParams(); + exportParams.setType(ExcelType.XSSF); + Workbook workbook = ExcelExportUtil.exportExcel(exportParams, entities, list); + CommonDownloadUtil.download(unionName + "分工会打卡人员.xlsx", workbook, response); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + /** + * 导出所选活动中尚未取得礼品券的报名人员。 + * + * @param response HTTP响应,用于写入XSSF格式Excel文件流 + * @param mainActivityId 主活动ID或relation:关联项目ID + * @param subActivityId 分活动ID,空值表示主活动汇总 + * @param activityId 健步走云端活动ID + * 无返回对象,通过response写出Excel文件流。 + */ + @Override + @Ok("void") + public void exportNotGetGiftVoucherUsers(HttpServletResponse response, String mainActivityId, String subActivityId, String activityId) { + List list = this.exportNotGetGiftVoucherUsers(mainActivityId, subActivityId, activityId); + List entities = new ArrayList<>(); + entities.add(new ExcelExportEntity("工号", "loginname", 20)); + entities.add(new ExcelExportEntity("姓名", "username", 20)); + entities.add(new ExcelExportEntity("单位", "unitname", 20)); +// entities.add(new ExcelExportEntity("手机号", "mobile", 20)); + try { + ExportParams exportParams = new ExportParams(); + exportParams.setType(ExcelType.XSSF); + Workbook workbook = ExcelExportUtil.exportExcel(exportParams, entities, list); + CommonDownloadUtil.download("未领取得奖品券人员.xlsx", workbook, response); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + /** + * 导出所选活动中尚未使用礼品券的人员。 + * + * @param response HTTP响应,用于写入XSSF格式Excel文件流 + * @param mainActivityId 主活动ID或relation:关联项目ID + * @param subActivityId 分活动ID,空值表示主活动汇总 + * @param activityId 健步走云端活动ID + * 无返回对象,通过response写出Excel文件流。 + */ + @Override + @Ok("void") + public void exportNotUseGiftVoucherUsers(HttpServletResponse response, String mainActivityId, String subActivityId, String activityId) { + List list = this.exportNotUseGiftVoucherUsers(mainActivityId, subActivityId, activityId); + List entities = new ArrayList<>(); + entities.add(new ExcelExportEntity("工号", "loginname", 20)); + entities.add(new ExcelExportEntity("姓名", "username", 20)); + entities.add(new ExcelExportEntity("单位", "unitname", 20)); +// entities.add(new ExcelExportEntity("手机号", "mobile", 20)); + try { + ExportParams exportParams = new ExportParams(); + exportParams.setType(ExcelType.XSSF); + Workbook workbook = ExcelExportUtil.exportExcel(exportParams, entities, list); + CommonDownloadUtil.download("导出未使用礼品券用户.xlsx", workbook, response); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + /** + * 按主分活动和达标筛选导出全部人员打卡汇总。 + * + * @param mainActivityId 主活动ID或relation:关联项目ID + * @param subActivityId 分活动ID,空值表示主活动汇总 + * @param activityId 健步走云端活动ID + * @param unionId 分工会ID,空值表示不追加筛选,已有数据权限仍生效 + * @param searchKeyword 姓名、工号或当前查询字段的检索内容 + * @param qualifiedStatus 达标筛选,qualified、unqualified或all + * @param response HTTP响应,用于写入XSSF格式Excel文件流 + * 无返回对象,通过response写出Excel文件流。 + */ + @Override + @Ok("void") + public void exportUserPunchExcel(String mainActivityId, String subActivityId, String activityId, String unionId, String searchKeyword, String qualifiedStatus, + HttpServletResponse response) { + List list = this.userPunchExportData(mainActivityId, subActivityId, activityId, unionId, searchKeyword, qualifiedStatus); + List entityList = new ArrayList<>(); + ExcelExportEntity no = new ExcelExportEntity("序号", "no", 10); + no.setFormat("isAddIndex"); + entityList.add(no); + entityList.add(new ExcelExportEntity("姓名", "username", 20)); + entityList.add(new ExcelExportEntity("工号", "loginname", 20)); + entityList.add(new ExcelExportEntity("分工会", "unionname", 20)); + entityList.add(new ExcelExportEntity("单位", "unitname", 40)); + entityList.add(new ExcelExportEntity("达标几个活动", "qualifiedActivityNum", 20)); + entityList.add(new ExcelExportEntity("点位总数", "pts_size", 20)); + entityList.add(new ExcelExportEntity("已打卡点位数", "sign_size", 20)); + entityList.add(new ExcelExportEntity("是否全部打卡完全部点位", "isAllSigned", 24)); + + try { + ExportParams exportParams = new ExportParams(); + exportParams.setType(ExcelType.XSSF); + Workbook workbook = ExcelExportUtil.exportExcel(exportParams, entityList, list); + CommonDownloadUtil.download("健步走人员打卡情况.xlsx", workbook, response); + } catch (Exception e) { + e.printStackTrace(); + } } } diff --git a/src/main/java/com/budwk/app/zhgh/activity/fitnesswalk/service/impl/FitnessWalkStepRaffleServiceImpl.java b/src/main/java/com/budwk/app/zhgh/activity/fitnesswalk/service/impl/FitnessWalkStepRaffleServiceImpl.java index 13e9be5..e505e2c 100644 --- a/src/main/java/com/budwk/app/zhgh/activity/fitnesswalk/service/impl/FitnessWalkStepRaffleServiceImpl.java +++ b/src/main/java/com/budwk/app/zhgh/activity/fitnesswalk/service/impl/FitnessWalkStepRaffleServiceImpl.java @@ -1,15 +1,27 @@ package com.budwk.app.zhgh.activity.fitnesswalk.service.impl; +import cn.afterturn.easypoi.excel.ExcelExportUtil; +import cn.afterturn.easypoi.excel.entity.ExportParams; +import cn.afterturn.easypoi.excel.entity.enmus.ExcelType; +import cn.afterturn.easypoi.excel.entity.params.ExcelExportEntity; import cn.hutool.core.date.DateUtil; +import cn.hutool.core.util.StrUtil; +import com.budwk.app.base.dao.CndPlus; import com.budwk.app.base.param.PageForm; +import com.budwk.app.base.result.Result; +import com.budwk.app.base.service.BaseService; import com.budwk.app.base.service.impl.BaseServiceImpl; +import com.budwk.app.base.utils.CommonDownloadUtil; import com.budwk.app.zhgh.activity.fitnesswalk.model.FitnessWalkActivity; import com.budwk.app.zhgh.activity.fitnesswalk.model.FitnessWalkAwardUser; import com.budwk.app.zhgh.activity.fitnesswalk.model.FitnessWalkRaffleRules; import com.budwk.app.zhgh.activity.fitnesswalk.model.FitnessWalkRaffleUser; import com.budwk.app.zhgh.activity.fitnesswalk.service.FitnessWalkCommonService; import com.budwk.app.zhgh.activity.fitnesswalk.service.FitnessWalkStepRaffleService; +import com.budwk.app.zhgh.dayofficework.integral.model.IntegralDetail; +import org.apache.poi.ss.usermodel.Workbook; import org.nutz.aop.interceptor.ioc.TransAop; +import org.nutz.dao.Chain; import org.nutz.dao.Cnd; import org.nutz.dao.Dao; import org.nutz.dao.Sqls; @@ -19,13 +31,17 @@ import org.nutz.dao.util.cri.Static; import org.nutz.ioc.aop.Aop; import org.nutz.ioc.loader.annotation.Inject; import org.nutz.ioc.loader.annotation.IocBean; +import org.nutz.lang.Lang; import org.nutz.lang.Strings; import org.nutz.lang.random.R; import org.nutz.lang.util.NutMap; - +import org.nutz.mvc.annotation.Ok; import java.util.*; +import java.util.ArrayList; +import java.util.List; import java.util.stream.Collectors; import java.util.stream.IntStream; +import javax.servlet.http.HttpServletResponse; /** * @version 1.0 @@ -36,6 +52,9 @@ import java.util.stream.IntStream; */ @IocBean(args = {"refer:dao"}) public class FitnessWalkStepRaffleServiceImpl extends BaseServiceImpl implements FitnessWalkStepRaffleService { + + @Inject + private BaseService baseService; public FitnessWalkStepRaffleServiceImpl(Dao dao) { super(dao); } @@ -43,14 +62,6 @@ public class FitnessWalkStepRaffleServiceImpl extends BaseServiceImpl reachList = manualLotteryRules.stream().filter(v -> "达标".equals(v.getRaffleType())) - .sorted(Comparator.comparing(FitnessWalkRaffleRules.ManualLotteryRules::getRankingPosition)).toList(); + // 达标奖项不使用排名位次,按页面配置顺序处理,避免对空的 rankingPosition 进行排序。 + List reachList = manualLotteryRules.stream() + .filter(v -> "达标".equals(v.getRaffleType())).toList(); for (FitnessWalkRaffleRules.ManualLotteryRules reach : reachList) { List nutMaps; if (reach.getIsSimultaneously() != null && reach.getIsSimultaneously()) { - // 可以兼得, 达标了的抽奖就是全部达标的人 + // 可以兼得 nutMaps = list; } else { - // 不可兼得, 这里要排除其他奖项的中奖人员 + // 不可兼得 nutMaps = list.stream().filter(v -> !winningRankLoginNames.contains(v.getString("loginname"))).collect(Collectors.toList()); } - // 抽奖人数 Integer lotteryUserNum = reach.getLotteryUserNum(); - // 奖项名称 String awardName = reach.getAwardName(); - // 这里是随机打乱了再去抽奖 List reachNutMaps = nutMaps.stream().collect(Collectors.collectingAndThen(Collectors.toList(), collected -> { - int size = Math.min(lotteryUserNum, collected.size()); Collections.shuffle(collected); - return collected.subList(0, size); + return collected.subList(0, lotteryUserNum - 1); })); List collect = reachNutMaps.stream().map(v -> { @@ -341,20 +347,263 @@ public class FitnessWalkStepRaffleServiceImpl extends BaseServiceImpl list = this.listMap(sql); + + List entityList = new ArrayList<>(); + ExcelExportEntity no = new ExcelExportEntity("序号", "no", 10); + no.setFormat("isAddIndex"); + entityList.add(no); + entityList.add(new ExcelExportEntity("工号", "loginname", 20)); + entityList.add(new ExcelExportEntity("姓名", "username", 20)); + entityList.add(new ExcelExportEntity("分工会", "unionname", 20)); + entityList.add(new ExcelExportEntity("单位", "unitname", 40)); + entityList.add(new ExcelExportEntity("达标天数", "standardsDays", 20)); + entityList.add(new ExcelExportEntity("步数", "total_steps", 20)); + + try { + ExportParams exportParams = new ExportParams(); + exportParams.setType(ExcelType.XSSF); + Workbook workbook = ExcelExportUtil.exportExcel(exportParams, entityList, list); + CommonDownloadUtil.download("健步走达标人员名单.xlsx", workbook, response); + } catch (Exception e) { + e.printStackTrace(); + } + } } diff --git a/src/main/java/com/budwk/app/zhgh/activity/fitnesswalk/utils/WeAppCloudUtil.java b/src/main/java/com/budwk/app/zhgh/activity/fitnesswalk/utils/WeAppCloudUtil.java index 84e0b52..b3328eb 100644 --- a/src/main/java/com/budwk/app/zhgh/activity/fitnesswalk/utils/WeAppCloudUtil.java +++ b/src/main/java/com/budwk/app/zhgh/activity/fitnesswalk/utils/WeAppCloudUtil.java @@ -1,22 +1,25 @@ package com.budwk.app.zhgh.activity.fitnesswalk.utils; +import org.nutz.aop.interceptor.ioc.TransAop; + +import org.nutz.ioc.aop.Aop; + import cn.hutool.core.util.StrUtil; import cn.hutool.http.HttpUtil; -import cn.hutool.json.JSONObject; -import cn.hutool.json.JSONUtil; +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONObject; import com.budwk.app.base.constant.RedisConstant; +import java.io.File; +import java.util.*; import lombok.AllArgsConstructor; import lombok.Data; import lombok.EqualsAndHashCode; import lombok.Getter; +import org.nutz.http.Http; import org.nutz.integration.jedis.RedisService; import org.nutz.ioc.loader.annotation.Inject; import org.nutz.ioc.loader.annotation.IocBean; - -import java.io.File; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.Map; +import org.nutz.json.Json; /** * 微信小程序云开发工具类 @@ -32,18 +35,18 @@ public class WeAppCloudUtil { @Inject public RedisService redisService; + @Aop(TransAop.READ_COMMITTED) public JSONObject request(CRUD crud, String sql) { String url = crud.url.replace("ACCESS_TOKEN", getAccessToken()); - String responseJson = HttpUtil.createPost(url).header("Content-Type", "application/json") - .body(JSONUtil.toJsonStr(Map.of("env", ENV, "query", sql))).execute().body(); - JSONObject response = JSONUtil.parseObj(responseJson); - if (response.getInt("errcode") == 42001) { + String responseJson = HttpUtil.createPost(url).header("Content-Type", "application/json").body(JSON.toJSONString(Map.of("env", ENV, "query", sql))).execute().body(); + JSONObject response = JSON.parseObject(responseJson); + if (response.getIntValue("errcode") == 42001) { redisService.del(RedisConstant.REDIS_KEY_WE_APP_ACCESS_TOKEN); // request(crud, sql); - } else if (response.getInt("errcode") == 40001) { + } else if (response.getIntValue("errcode") == 40001) { redisService.del(RedisConstant.REDIS_KEY_WE_APP_ACCESS_TOKEN); - } else if (response.getInt("errcode") != 0) { - throw new RuntimeException("小程序云函数接口调用失败,errcode:" + response.getStr("errcode") + ",errmsg:" + response.getStr("errmsg")); + } else if (response.getIntValue("errcode") != 0) { + throw new RuntimeException("小程序云函数接口调用失败,errcode:" + response.getString("errcode") + ",errmsg:" + response.getString("errmsg")); } return response; } @@ -60,9 +63,7 @@ public class WeAppCloudUtil { } String url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET".replace("APPID", APPID).replace("APPSECRET", APP_SECRET); String responseJson = HttpUtil.createGet(url).execute().body(); - - TokenResponse tokenResponse = JSONUtil.toBean(responseJson, TokenResponse.class); - // 微信的Access_token,两个小时过期,也就是7200秒 + TokenResponse tokenResponse = JSON.parseObject(responseJson, TokenResponse.class); redisService.setex(RedisConstant.REDIS_KEY_WE_APP_ACCESS_TOKEN, 7000, tokenResponse.getAccess_token()); return tokenResponse.getAccess_token(); @@ -109,26 +110,26 @@ public class WeAppCloudUtil { * @param file * @return */ + @Aop(TransAop.READ_COMMITTED) public String uploadFile(String rootPath, File file) { String path = rootPath + file.getName(); String url = "https://api.weixin.qq.com/tcb/uploadfile?access_token=ACCESS_TOKEN".replace("ACCESS_TOKEN", getAccessToken()); - String responseJson = HttpUtil.createPost(url).header("Content-Type", "application/json") - .body(JSONUtil.toJsonStr(Map.of("env", ENV, "path", path))).execute().body(); - JSONObject jsonObject = JSONUtil.parseObj(responseJson); - if (jsonObject.getInt("errcode") != 0) { - throw new RuntimeException(jsonObject.getStr("errmsg")); + String responseJson = HttpUtil.createPost(url).header("Content-Type", "application/json").body(JSON.toJSONString(Map.of("env", ENV, "path", path))).execute().body(); + JSONObject jsonObject = JSON.parseObject(responseJson); + if (jsonObject.getIntValue("errcode") != 0) { + throw new RuntimeException(jsonObject.getString("errmsg")); } try{ - HttpUtil.createPost(jsonObject.getStr("url")) + HttpUtil.createPost(jsonObject.getString("url")) .form("key", path) - .form("Signature", jsonObject.getStr("authorization")) - .form("x-cos-security-token", jsonObject.getStr("token")) - .form("x-cos-meta-fileid", jsonObject.getStr("cos_file_id")) + .form("Signature", jsonObject.getString("authorization")) + .form("x-cos-security-token", jsonObject.getString("token")) + .form("x-cos-meta-fileid", jsonObject.getString("cos_file_id")) .form("file", file) .execute().body(); - return jsonObject.getStr("file_id"); + return jsonObject.getString("file_id"); }catch (Exception e){ - return jsonObject.getStr("file_id"); + return jsonObject.getString("file_id"); } } @@ -153,18 +154,16 @@ public class WeAppCloudUtil { body.put("file_list", fm); - String responseJson = HttpUtil.createPost(url) .header("Content-Type", "application/json") - .body(JSONUtil.toJsonStr(body)) + .body(JSON.toJSONString(body)) .execute().body(); - System.out.println(JSONUtil.toJsonStr(body)); - JSONObject jsonObject = JSONUtil.parseObj(responseJson); - if (jsonObject.getInt("errcode") != 0) { - throw new RuntimeException(jsonObject.getStr("errmsg")); + System.out.println(JSON.toJSONString(body)); + JSONObject jsonObject = JSON.parseObject(responseJson); + if (jsonObject.getIntValue("errcode") != 0) { + throw new RuntimeException(jsonObject.getString("errmsg")); } - return jsonObject.getJSONArray("file_list").toList(JSONObject.class).get(0).getStr("download_url"); + return jsonObject.getJSONArray("file_list").toJavaList(JSONObject.class).get(0).getString("download_url"); } - } diff --git a/src/main/resources/static/assets/platform/js/mixin/initTableMixins.js b/src/main/resources/static/assets/platform/js/mixin/initTableMixins.js index 9761855..6c1592b 100644 --- a/src/main/resources/static/assets/platform/js/mixin/initTableMixins.js +++ b/src/main/resources/static/assets/platform/js/mixin/initTableMixins.js @@ -69,11 +69,12 @@ const initTableMixins = { const address = this.pageDataUrl ? this.pageDataUrl : loc() + "/pageData" this.tableLoading = true this.$axios.post(address, data ? data : this.pageForm).then((res) => { - this.tableLoading = false if (res.code === 0) { this.tableData = res.data.list this.pageForm.totalCount = res.data.totalCount } + }).finally(() => { + this.tableLoading = false }) }, notifySuccess(msg) { diff --git a/src/main/resources/views/platform/zhgh/activity/fitnesswalk/activityManage.html b/src/main/resources/views/platform/zhgh/activity/fitnesswalk/activityManage.html index 87d94c8..6da20a4 100644 --- a/src/main/resources/views/platform/zhgh/activity/fitnesswalk/activityManage.html +++ b/src/main/resources/views/platform/zhgh/activity/fitnesswalk/activityManage.html @@ -1,4 +1,4 @@ - + +
- - - - - - - - - - - - + - - - - 批量删除  - - - - - 删除已选 - - - 全部删除 - - - + + + + 批量删除  + + + + + 删除已选 + + + 全部删除 + + + + v-loading="tableLoading" @selection-change="handleSelectionChange"> @@ -88,18 +98,13 @@ layout("/layouts/platform.html"){
- diff --git a/src/main/resources/views/platform/zhgh/activity/fitnesswalk/punchStatistics.html b/src/main/resources/views/platform/zhgh/activity/fitnesswalk/punchStatistics.html index 30519a8..4b36ec8 100644 --- a/src/main/resources/views/platform/zhgh/activity/fitnesswalk/punchStatistics.html +++ b/src/main/resources/views/platform/zhgh/activity/fitnesswalk/punchStatistics.html @@ -1,59 +1,90 @@ -
- - - - - - - + + + + + + + + @@ -111,15 +150,24 @@ layout("/layouts/platform.html"){
- - - - - - - - - + + + + + + + + + + - diff --git a/src/main/resources/views/platform/zhgh/activity/fitnesswalk/stepRanking.html b/src/main/resources/views/platform/zhgh/activity/fitnesswalk/stepRanking.html index 25d7869..1be7b16 100644 --- a/src/main/resources/views/platform/zhgh/activity/fitnesswalk/stepRanking.html +++ b/src/main/resources/views/platform/zhgh/activity/fitnesswalk/stepRanking.html @@ -7,69 +7,121 @@ layout("/layouts/platform.html"){
- - - - - + - - - 今日排行榜 - 总排行榜 + + + {{item.name}} - 导出{{pageForm.mode == 'today' ? '今日排行榜' : '总排行榜'}} + 导出{{rankingTimeRangeName}} + + - +
- diff --git a/src/main/resources/views/platform/zhgh/activity/fitnesswalk/stepStatistics.html b/src/main/resources/views/platform/zhgh/activity/fitnesswalk/stepStatistics.html index c0e42f1..bc4adbd 100644 --- a/src/main/resources/views/platform/zhgh/activity/fitnesswalk/stepStatistics.html +++ b/src/main/resources/views/platform/zhgh/activity/fitnesswalk/stepStatistics.html @@ -5,73 +5,92 @@ layout("/layouts/platform.html"){