This commit is contained in:
2025-11-01 11:19:35 +08:00
10 changed files with 345 additions and 95 deletions
@@ -14,7 +14,7 @@ import java.util.Base64;
* @Version 1.0
* @Description TODO
*/
public class RsaUtils {
public class RsaUtil {
static {
Security.addProvider(new BouncyCastleProvider());
}
@@ -1,7 +1,6 @@
package com.budwk.app.sys.controller;
import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.codec.Base64Decoder;
import cn.hutool.core.codec.Base64Encoder;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.StrUtil;
@@ -13,7 +12,7 @@ import com.budwk.app.base.enums.LoginType;
import com.budwk.app.base.exception.BaseException;
import com.budwk.app.base.interceptor.sLog.SLogService;
import com.budwk.app.base.result.Result;
import com.budwk.app.base.utils.RsaUtils;
import com.budwk.app.base.utils.RsaUtil;
import com.budwk.app.sys.models.Sys_log;
import com.budwk.app.sys.models.Sys_user;
import com.budwk.app.sys.services.SysUserService;
@@ -42,13 +41,7 @@ import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.Base64;
import java.util.Map;
import java.util.UUID;
@@ -269,9 +262,9 @@ public class SysLoginController {
public Object publicKey() {
try {
// 生成密钥对
KeyPair keyPair = RsaUtils.generateKeyPair();
String publicKeyStr = RsaUtils.getPublicKeyBase64(keyPair.getPublic());
String privateKeyStr = RsaUtils.getPrivateKeyBase64(keyPair.getPrivate());
KeyPair keyPair = RsaUtil.generateKeyPair();
String publicKeyStr = RsaUtil.getPublicKeyBase64(keyPair.getPublic());
String privateKeyStr = RsaUtil.getPrivateKeyBase64(keyPair.getPrivate());
// 生成 UUID 作为 keyId
String keyId = UUID.randomUUID().toString().replace("-", "");
@@ -2,8 +2,7 @@ package com.budwk.app.web.commons.auth.service;
import com.budwk.app.base.constant.RedisConstant;
import com.budwk.app.base.exception.BaseException;
import com.budwk.app.base.result.Result;
import com.budwk.app.base.utils.RsaUtils;
import com.budwk.app.base.utils.RsaUtil;
import com.wf.captcha.ArithmeticCaptcha;
import lombok.extern.slf4j.Slf4j;
import org.nutz.integration.jedis.RedisService;
@@ -14,7 +13,6 @@ import org.nutz.lang.random.R;
import org.nutz.lang.util.NutMap;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.Base64;
@@ -118,6 +116,6 @@ public class ValidateService {
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(privateKeyBytes);
PrivateKey privateKey = KeyFactory.getInstance("RSA").generatePrivate(keySpec);
// 解密密码
return RsaUtils.decrypt(password, privateKey);
return RsaUtil.decrypt(password, privateKey);
}
}
@@ -10,6 +10,7 @@ import com.budwk.app.flow.entity.ProcessInstance;
import com.budwk.app.flow.enums.ProcessInstanceStateEnum;
import com.budwk.app.sys.models.Sys_home_activity;
import com.budwk.app.sys.models.Sys_union;
import com.budwk.app.sys.models.Sys_unit;
import com.budwk.app.zhgh.activity.trainSignUp.models.*;
import com.budwk.app.zhgh.activity.trainSignUp.service.TrainSignUpActivityService;
import com.budwk.app.zhgh.club.model.SysClub;
@@ -206,14 +207,14 @@ public class TrainSignUpManageController {
@Ok("json:full")
@ApiOperation("查询分工会和协会")
@SaCheckPermission("trainSignUp.manage")
public Result selectUnionAndClub() {
public Result selectUnitAndClub() {
List<NutMap> result = new ArrayList<>();
List<Sys_union> unionList = dao.query(Sys_union.class, Cnd.NEW().asc(Sys_union::getUnionCode));
for (Sys_union union : unionList) {
List<Sys_unit> unitList = dao.query(Sys_unit.class, Cnd.NEW().asc(Sys_unit::getUnitcode));
for (Sys_unit unit : unitList) {
NutMap map = new NutMap();
map.put("id", union.getId());
map.put("name", union.getName());
map.put("type", "union");
map.put("id", unit.getId());
map.put("name", unit.getName());
map.put("type", "unit");
result.add(map);
}
List<SysClub> clubList = dao.query(SysClub.class, Cnd.NEW().asc(SysClub::getClubCode));
@@ -65,12 +65,18 @@ public class TrainsignUpMineController {
public void h5Index() {
}
@At("/scan")
@SaCheckPermission("h5.trainSignUp.mine")
@Ok("beetl:/platform/zhghh5/activity/trainSignUp/scan/index.html")
public void scan() {
}
@At
@ApiOperation("主动扫码签到")
@Aop(TransAop.READ_COMMITTED)
@SaCheckPermission(value = {"trainSignUp.mine", "h5.trainSignUp.mine"}, mode = SaMode.OR)
@SLog(tag = "品牌活动-活动签到", msg = "主动扫码签到")
public Result drivingScan(String courseId) {
public Result drivingScan(String courseId, Double lat, Double lng) {
// 主动扫码签到是用户自己打开扫一扫,扫二维码签到
int count = dao.count(TrainSignUpUserCourse.class, Cnd.where("courseId", "=", courseId).and("userId", "=", SecurityUtil.getUserId()));
@@ -79,7 +85,9 @@ public class TrainsignUpMineController {
}
TrainSignUpCourse course = dao.fetch(TrainSignUpCourse.class, courseId);
List<Double> coordinates = course.getCourseLocationCoordinates();
int minute = course.getSignUnit() != null ? course.getSignUnit() : 0;
int radius = course.getRadius() != null ? course.getRadius() : 100;
boolean flag = true;
// 获取现在的日期,并根据设置的数据
@@ -96,9 +104,16 @@ public class TrainsignUpMineController {
if(flag) {
return Result.error(99, "未到签到时间");
}
double distance = DistanceUtils.getDistance(coordinates.get(0), coordinates.get(1), lat, lng);
if(distance > radius) {
int result = BigDecimal.valueOf(distance).setScale(0, RoundingMode.HALF_UP).intValue();
return Result.error(99, "签到失败,您距离签到点还有%s米".formatted(result));
}
dao.update(timeList);
return Result.success();
return Result.success("签到成功");
}
@At
@@ -138,15 +153,23 @@ public class TrainsignUpMineController {
public Result positionSign(String courseId, String timeId, Double lat, Double lng) {
TrainSignUpCourse course = dao.fetch(TrainSignUpCourse.class, courseId);
List<Double> coordinates = course.getCourseLocationCoordinates();
double distance = DistanceUtils.getDistance(coordinates.get(0), coordinates.get(1), lat, lng);
int minute = course.getSignUnit() != null ? course.getSignUnit() : 0;
int radius = course.getRadius() != null ? course.getRadius() : 100;
TrainSignUpUserCourse userCourse = dao.fetch(TrainSignUpUserCourse.class, timeId);
DateTime startTime = DateUtil.offsetMinute(userCourse.getCourseStartTime(), -minute);
DateTime endTime = DateUtil.offsetMinute(userCourse.getCourseEndTime(), minute);
if(!DateUtil.isIn(DateUtil.date(), startTime, endTime)) {
return Result.error(99, "未到签到时间");
}
double distance = DistanceUtils.getDistance(coordinates.get(0), coordinates.get(1), lat, lng);
if(distance > radius) {
int result = BigDecimal.valueOf(distance).setScale(0, RoundingMode.HALF_UP).intValue();
return Result.error(99, "签到失败,您距离签到点还有%s米".formatted(result));
}
TrainSignUpUserCourse userCourse = dao.fetch(TrainSignUpUserCourse.class, timeId);
userCourse.setAttend(true);
userCourse.setAttendTime(DateUtil.date());
dao.update(userCourse);
@@ -193,4 +216,11 @@ public class TrainsignUpMineController {
List<NutMap> listMap = activityService.listMap(sql);
return Result.success(listMap);
}
@At
@SaCheckPermission(value = {"trainSignUp.mine", "h5.trainSignUp.mine"}, mode = SaMode.OR)
public Result fetchCourse(String courseId) {
TrainSignUpCourse course = dao.fetch(TrainSignUpCourse.class, courseId);
return Result.success(course);
}
}
@@ -14,6 +14,7 @@ import com.budwk.app.zhgh.activity.family.service.FamilyActivityStatisticsServic
import com.budwk.app.zhgh.activity.trainSignUp.models.*;
import com.budwk.app.zhgh.activity.trainSignUp.service.TrainSignUpActivityService;
import com.budwk.app.zhgh.activity.trainSignUp.service.TrainSignUpActivityStatisticsService;
import com.google.common.collect.Sets;
import lombok.extern.slf4j.Slf4j;
import org.nutz.aop.interceptor.async.Async;
import org.nutz.aop.interceptor.ioc.TransAop;
@@ -29,10 +30,7 @@ import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.lang.Lang;
import org.nutz.lang.util.NutMap;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;
@@ -80,72 +78,28 @@ public class TrainSignUpActivityServiceImpl extends BaseServiceImpl<TrainSignUpA
@Override
@Aop(TransAop.READ_COMMITTED)
public void edit(TrainSignUpActivity activity) {
//修改活动
// 1. 更新主活动
update(activity);
//修改类型限制
// 2. 处理类型限制
List<TrainSignUpTypeLimit> typeLimits = activity.getTypeLimits();
typeLimits.forEach(v -> v.setActivityId(activity.getId()));
if(Lang.isNotEmpty(typeLimits)) {
if (Lang.isNotEmpty(typeLimits)) {
typeLimits.forEach(limit -> limit.setActivityId(activity.getId()));
insertOrUpdate(typeLimits);
}
// 3. 处理课程列表
List<TrainSignUpCourse> courseList = activity.getCourseList();
courseList.forEach(v -> {
v.setActivityId(activity.getId());
dao().insertOrUpdate(v);
if (Lang.isNotEmpty(v.getCourseTimeList())) {
this.setCourseTimeAndInsert(v);
if (Lang.isEmpty(courseList)) {
return;
}
for (TrainSignUpCourse course : courseList) {
course.setActivityId(activity.getId());
dao().insertOrUpdate(course);
if (Lang.isNotEmpty(course.getCourseTimeList())) {
handleCourseTimes(activity, course);
}
});
//查询原来的活动
List<TrainSignUpCourse> oldCourseList = dao().query(TrainSignUpCourse.class, Cnd.where("activityId", "=", activity.getId()));
//原来的培训班id
List<String> oldCourseIdList = oldCourseList.stream().map(TrainSignUpCourse::getId).toList();
//原来的上课时间
List<TrainSignUpActivityCourse> oldActCourseTimeList = dao().query(TrainSignUpActivityCourse.class, Cnd.where("activityId", "=", activity.getId()));
//现在的上课时间
List<String> nowCourseTimeListId = new ArrayList<>();
activity.getCourseList().forEach(v -> {
if (v.getCourseTimeList() != null) {
nowCourseTimeListId.addAll(v.getCourseTimeList().stream().map(TrainSignUpActivityCourse::getId).toList());
}
});
List<String> deleteCourseTimeListId = oldActCourseTimeList.stream().map(TrainSignUpActivityCourse::getId).filter(id -> !nowCourseTimeListId.contains(id)).collect(Collectors.toList());
List<String> courseIdList = courseList.stream().map(TrainSignUpCourse::getId).collect(Collectors.toList());
//删除关联的培训班
List<String> deleteIdList = oldCourseIdList.stream().filter(v -> !courseIdList.contains(v)).collect(Collectors.toList());
dao().clear(TrainSignUpCourse.class, Cnd.where("id", "in", deleteIdList));
dao().clear(TrainSignUpActivityCourse.class, Cnd.where("id", "in", deleteCourseTimeListId));
dao().clear(TrainSignUpUser.class, Cnd.where("courseId", "not in", courseIdList).and("activityId", "=", activity.getId()));
dao().clear(TrainSignUpUserCourse.class, Cnd.where("courseId", "not in", courseIdList).and("activityId", "=", activity.getId()));
//查询修改过培训时间的记录
Sql tsuucSql = Sqls.create("""
SELECT
tsuuc.id,
tsuac.courseStartTime,
tsuac.courseEndTime
FROM
`train_sign_up_user_course` tsuuc
LEFT JOIN train_sign_up_activity_course tsuac ON tsuac.id = tsuuc.activityCourseId
where tsuuc.courseStartTime != tsuac.courseStartTime or tsuuc.courseEndTime != tsuac.courseEndTime
""");
List<NutMap> tsuucList = listMap(tsuucSql);
tsuucList.forEach(v -> {
Chain chain = Chain.make("courseStartTime", v.getTime("courseStartTime"));
chain.add("courseEndTime", v.getTime("courseEndTime"));
Cnd cnd = Cnd.where("id", "=", v.getString("id"));
dao().update("train_sign_up_user_course", chain, cnd);
});
}
// 4. 同步首页活动
if (!activity.isDisabled()) {
Sys_home_activity sysHomeActivity = activity.covertToSysHomeActivity();
dao().insertOrUpdate(sysHomeActivity);
@@ -154,6 +108,101 @@ public class TrainSignUpActivityServiceImpl extends BaseServiceImpl<TrainSignUpA
}
}
private void handleCourseTimes(TrainSignUpActivity activity, TrainSignUpCourse course) {
String courseId = course.getId();
String activityId = activity.getId();
// 1. 获取新旧时间 ID 集合
Set<String> newTimeIds = course.getCourseTimeList().stream()
.map(TrainSignUpActivityCourse::getId)
.filter(StrUtil::isNotBlank)
.collect(Collectors.toSet());
List<TrainSignUpActivityCourse> oldAcList = dao().query(TrainSignUpActivityCourse.class,
Cnd.where("courseId", "=", courseId));
Set<String> oldTimeIds = oldAcList.stream()
.map(TrainSignUpActivityCourse::getId)
.filter(StrUtil::isNotBlank)
.collect(Collectors.toSet());
// 2. 删除已移除的时间段及其关联用户课程
Set<String> deletedTimeIds = Sets.difference(oldTimeIds, newTimeIds).immutableCopy();
if (!deletedTimeIds.isEmpty()) {
dao().clear(TrainSignUpActivityCourse.class, Cnd.where("id", "in", deletedTimeIds));
dao().clear(TrainSignUpUserCourse.class, Cnd.where("activityCourseId", "in", deletedTimeIds));
}
// 3. 插入/更新课程时间段(假设 setCourseTimeAndInsert 是插入新时间段)
setCourseTimeAndInsert(course);
// 4. 批量处理用户课程关联
// 查询当前课程下的所有用户
List<TrainSignUpUser> users = dao().query(TrainSignUpUser.class, Cnd.where("courseId", "=", courseId));
if (users.isEmpty()) {
return;
}
// 查询已存在的用户课程记录(按 activityCourseId 分组)
List<TrainSignUpUserCourse> existingUserCourses = dao().query(TrainSignUpUserCourse.class,
Cnd.where("courseId", "=", courseId));
Set<String> existingActivityCourseIds = existingUserCourses.stream()
.map(TrainSignUpUserCourse::getActivityCourseId)
.collect(Collectors.toSet());
List<TrainSignUpUserCourse> toUpdate = new ArrayList<>();
List<TrainSignUpUserCourse> toInsert = new ArrayList<>();
for (TrainSignUpActivityCourse timeSlot : course.getCourseTimeList()) {
String timeId = timeSlot.getId();
if (StrUtil.isBlank(timeId)) continue;
if (existingActivityCourseIds.contains(timeId)) {
// 更新时间
toUpdate.add(buildUserCourseForUpdate(timeId, timeSlot.getCourseStartTime(), timeSlot.getCourseEndTime()));
} else {
// 为每个用户新增记录
for (TrainSignUpUser user : users) {
toInsert.add(buildNewUserCourse(activityId, courseId, user.getUserId(), timeId, timeSlot));
}
}
}
// 批量更新(可根据 DAO 能力合并为 batchUpdate
for (TrainSignUpUserCourse item : toUpdate) {
dao().update(TrainSignUpUserCourse.class,
Chain.make("courseStartTime", item.getCourseStartTime())
.add("courseEndTime", item.getCourseEndTime()),
Cnd.where("activityCourseId", "=", item.getActivityCourseId()));
}
// 批量插入
if (!toInsert.isEmpty()) {
dao().insert(toInsert);
}
}
private TrainSignUpUserCourse buildUserCourseForUpdate(String activityCourseId, Date startTime, Date endTime) {
TrainSignUpUserCourse u = new TrainSignUpUserCourse();
u.setActivityCourseId(activityCourseId);
u.setCourseStartTime(startTime);
u.setCourseEndTime(endTime);
return u;
}
private TrainSignUpUserCourse buildNewUserCourse(String activityId, String courseId, String userId,
String activityCourseId, TrainSignUpActivityCourse timeSlot) {
TrainSignUpUserCourse u = new TrainSignUpUserCourse();
u.setActivityId(activityId);
u.setCourseId(courseId);
u.setUserId(userId);
u.setActivityCourseId(activityCourseId);
u.setCourseStartTime(timeSlot.getCourseStartTime());
u.setCourseEndTime(timeSlot.getCourseEndTime());
u.setAttend(false);
u.setAttendTime(null);
return u;
}
private void setCourseTimeAndInsert(TrainSignUpCourse course) {
course.getCourseTimeList().forEach(courseTime -> {
Calendar calendar = Calendar.getInstance();
@@ -591,11 +591,11 @@ const basicForm = {
this.activityGroupList = await this.getActivityGroup()
this.historicalActList = await this.getHistoricalActList()
this.courseTypeList = await this.getAllType()
await this.selectUnionAndClub()
await this.selectUnitAndClub()
await this.init(row)
},
async selectUnionAndClub() {
const resp = await this.$axios.post("/platform/trainSignUp/manage/selectUnionAndClub")
async selectUnitAndClub() {
const resp = await this.$axios.post("/platform/trainSignUp/manage/selectUnitAndClub")
this.undertakeOptions = resp.data
},
},
@@ -66,7 +66,8 @@ const makeQrcode = {
this.courseDialog = true
},
makeCourseCode(row) {
const url = '${AppDomain!}/platform/trainSignUp/mine/drivingScan'
//const url = '${AppDomain!}/platform/trainSignUp/mine/drivingScan'
const url = '${AppDomain!}/platform/trainSignUp/mine/scan'
const data = url + '?courseId=' + row.id
console.log(data)
const content = jrQrcode.getQrBase64(data)
@@ -122,12 +122,19 @@ const times = {
callback(null);
return;
}
const loading = vant.Toast.loading({
message: "获取定位中...",
forbidClick: false,
loadingType: "spinner",
duration: 0,
})
navigator.geolocation.getCurrentPosition(
(position) => {
callback({
lat: position.coords.latitude,
lng: position.coords.longitude
});
loading.close()
},
(error) => {
let msg = '定位失败,请稍后重试';
@@ -144,6 +151,7 @@ const times = {
}
this.$toast(msg);
callback(null);
loading.close()
},
{
enableHighAccuracy: true,
@@ -0,0 +1,170 @@
<!--#
layout("/layouts/platform_h5.html"){
#-->
<style scoped>
.weui-msg {
min-height: calc(100vh - 40vh - 46px);
}
</style>
<div id="app">
<van-nav-bar title="活动签到" left-text="返回" left-arrow @click-left="historyBack" fixed placeholder></van-nav-bar>
<div id="mapContainer" style="width: 100%; height: 40vh"></div>
<div class="weui-msg">
<div class="weui-msg__icon-area">
<i v-if="res?.code !== 0" class="weui-icon-warn weui-icon_msg"></i>
<i v-if="res?.code === 0" class="weui-icon-success weui-icon_msg"></i>
</div>
<div class="weui-msg__text-area">
<div class="weui-msg__title">温馨提醒</div>
<div v-html="res?.msg"></div>
</div>
<div class="weui-msg__opr-area">
<p class="weui-btn-area">
<a v-if="res?.code !== 0" @click="res = {}; onSign()" class="weui-btn weui-btn_default">重新签到</a>
<a v-if="res?.code === 0" class="weui-btn weui-btn_primary">您已签到</a>
</p>
</div>
</div>
</div>
<script>
const vue = new Vue({
el: "#app",
store,
components: {
},
data() {
return {
courseId: GetQueryString('courseId'),
markerLayer: null,
map: null,
res: {},
}
},
methods: {
initMap() {
if(!this.row.courseLocationCoordinates) {
this.$toast('未设置签到点')
return
}
const posi = this.row.courseLocationCoordinates
const center = new TMap.LatLng(posi[0], posi[1])
this.map = new TMap.Map('mapContainer', {
resizeEnable: true,
zoom: 16,
center: center
})
this.markerLayer = new TMap.MultiMarker({
map: this.map,
styles: {
"current": new TMap.MarkerStyle({
"src": "https://mapapi.qq.com/web/bundles/lbs-home/prod/assets/markerActive-NhH9NoBV.png",
"width": 40, // 点标记样式宽度(像素)
})
},
geometries: []
})
this.createMarker(center)
this.createCircle(center)
},
createMarker(position, styleId = 'marker', id = 'marker_' + Date.now()) {
this.markerLayer.add([
{
id: id,
position: position,
styleId: styleId
}
]);
},
createCircle(position) {
this.circle = new TMap.MultiCircle({
map: this.map,
geometries: [{
center: position,
radius: this.row.radius || 100,
}],
});
},
getLocation(callback) {
if (typeof callback !== 'function') {
callback = () => {};
}
if (!navigator.geolocation) {
this.$toast('当前浏览器不支持定位功能');
callback(null);
return;
}
const loading = vant.Toast.loading({
message: "获取定位中...",
forbidClick: false,
loadingType: "spinner",
duration: 0,
})
navigator.geolocation.getCurrentPosition(
(position) => {
callback({
lat: position.coords.latitude,
lng: position.coords.longitude
});
loading.close()
},
(error) => {
let msg = '定位失败,请稍后重试';
switch (error.code) {
case error.PERMISSION_DENIED:
msg = '请允许浏览器获取位置信息';
break;
case error.POSITION_UNAVAILABLE:
msg = '无法获取当前位置';
break;
case error.TIMEOUT:
msg = '定位超时,请重试';
break;
}
this.$toast(msg);
callback(null);
loading.close()
},
{
enableHighAccuracy: true,
timeout: 10000,
maximumAge: 60000
}
);
},
async fetchCourse() {
const res = await this.$axios.post('/platform/trainSignUp/mine/fetchCourse', {courseId: this.courseId})
this.row = res.data
},
onSign() {
if(this.markerLayer) {
this.markerLayer.remove(["current"])
}
this.getLocation(async (coords) => {
if (coords) {
const center = new TMap.LatLng(coords.lat, coords.lng)
this.createMarker(center, 'current', 'current')
this.res = await this.$axios.post('/platform/trainSignUp/mine/drivingScan', {
courseId: this.row.id,
lat: coords.lat,
lng: coords.lng
})
}
})
}
},
async created() {
await this.fetchCourse()
this.initMap()
this.onSign()
}
})
</script>
<!--#
}
#-->