登山节微信网页实现思路简述 | 实现定位和数据处理

grtsinry43
3/23/2025
83 views
预计阅读时长 19 分钟

AI Summary

Powered By DeepSeek-R1
|

去年 10 月份左右,学校组织活动需要,要求 7 天内实现一个登山节打卡用微信网页,包含定位打卡,排行榜,抽奖等等功能,其实当时一周内我就已经完成并迭代至稳定,但是当时这个博客还没有重构完故没有记录,如今新一期的活动准备开始,新功能和管理面板的开发需求也已经接近完成。但还没完成 (硬控了我好久),分享一下核心功能的实现思路。

定位区域和有效范围实现

image

首先最重要的功能就是确定打卡范围,我的想法是最低成本实现且对精度没有高要求,就确认一个中心点,然后用得到的坐标和这个坐标计算距离,小于要求距离即可,实现大概是这样:

java
1private boolean isWithinCheckinRange(BigDecimal latitude, BigDecimal longitude, int type) {
2        // 这里从 checkpoint 表中获取签到点的经纬度
3        CheckPoint checkPoint = checkPointService.getById(type);
4
5        BigDecimal checkinLatitude = checkPoint.getLatitude();
6        BigDecimal checkinLongitude = checkPoint.getLongitude();
7
8        // 计算提供的坐标和签到点之间的距离
9        double distance = calculateDistance(latitude, longitude, checkinLatitude, checkinLongitude);
10

问题来了,国内为了隐私考虑,采用了 gcj02 坐标体系,而只有通用的 wgs84 坐标才能进行计算得到距离,好在这方面已有很多现成开源的算法实现,我们可以这样:

typescript
1const a = 6378245.0;
2const ee = 0.00669342162296594323;
3
4function outOfChina(lng: number, lat: number): boolean {
5    return (lng < 72.004 || lng > 137.8347 || lat < 0.8293 || lat > 55.8271);
6}
7
8function transformLat(x: number, y: number): number {
9    let ret = -100.0 + 2.0 * x + 3.0 * y + 0.2 * y * y + 0.1 * x * y + 0.2 * Math.sqrt(Math.abs(x));
10    ret += (20.0 * Math.sin(6.0 * x * Math.PI) + 20.0 * Math.sin(2.0 * x * Math.PI)) * 2.0 / 3.0;

到了这一步,我们只需要遍历每个点和提交数据的范围就好了,但是遍历...,为了防止给后端造成计算压力,我们这里的计算是分两个方面的。

首先为了地图上范围的标注,前端本身就会拉取签到点信息,那我们就可以利用这个,在前端就做好一次距离计算,仅在范围内才会允许发起签到,并且这里还会指定哪个签到点,这样就分散了计算压力,也就是这样:

typescript
1matchedPoint.value = checkPoints.value.find(point => {
2        const distance = AMap.GeometryUtil.distance([res.longitude, res.latitude], [point.longitude, point.latitude]);
3        return distance <= 50;
4      });
5
6      if (matchedPoint.value) {
7        if (currentStep.value === 0 || !matchedPoint.value.isEnd) {
8          form.value.type = matchedPoint.value.id;
9        }
10        if (currentStep.value === 1 && !matchedPoint.value.isEnd) {

而后端最后准备要计算的大概是这样的,这样只需要判断是否符合签到进度和签到范围就可以啦:

java
1package com.csu54sher.csudynamicyouth.dto;
2
3@Data
4public class CheckInInfo {
5    private BigDecimal latitude;
6    private BigDecimal longitude;
7    // 这个 type 要对应 checkpoint 的 id
8    private int type;
9}

简单的加密和防修改手段

这里不能说得太详细,毕竟这一次活动还没开始呢,但是实际活动中...emm 大部分都在玩虚拟定位,这个从根本上数据就不可信,并且我们的软件载体还是微信网页,没有防范的方法(其实也有,这次灰度了几个,我看看效果,用打 tag 的方式)

首先是为了防止重放攻击,我们的核心经纬度数据是经过算法加密的,同时也算了一个 state 带了一个时间戳:

java
1package com.csu54sher.csudynamicyouth.dto;
2
3import jakarta.validation.constraints.NotBlank;
4import jakarta.validation.constraints.NotNull;
5import lombok.Data;
6
7/**
8 * @author grtsinry43
9 * @date 2024/10/19 10:29
10 * @description 热爱可抵岁月漫长

面对面组队的简单实现

emm...这功能被砍掉了啊啊啊啊啊啊啊啊啊啊啊!!!

image

这个的核心功能就是发起组队会生成一个组队码,规定时间内,其他人输入这个相同的号码就可以进去,有点类似面对面建群。

生成和存储

组队码是随机生成的四位数,在判断不重复之后插入数据库和 Redis 中,有效期五分钟,在过期之前,收到请求时都会从 redis 取出,然后将用户团队对象插入/删除。

具体实现是这样的:

java
1public void joinTeam(String teamCode, Long userId) {
2        // 这里没有就是错误
3        TempTeam team = lambdaQuery().eq(TempTeam::getPwd, teamCode).one();
4        if (team == null) {
5            throw new BusinessException(ErrorCode.NOT_FOUND);
6        }
7
8        // 这里没了就是过期
9        TempTeam redisTeam = (TempTeam) redisService.get("temp_team_" + team.getId());
10        if (redisTeam == null) {

实时列表刷新

组队列表还要求实时性,于是我们需要一个服务器端事件(SSE)来实现:

首先是事件触发的部分:

java
1public void addEmitter(Long teamId, SseEmitter emitter) {
2        emitters.put(teamId, emitter);
3    }
4
5    public void removeEmitter(Long teamId) {
6        emitters.remove(teamId);
7    }
8
9    public void notifyTeamUpdate(Long teamId) {
10        SseEmitter emitter = emitters.get(teamId);

然后提供一个服务器事件的 api 端点:

java
1@GetMapping(value = "/stream/{id}", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
2    public ResponseEntity<SseEmitter> streamTeamUpdates(@PathVariable Long id) {
3        TempTeam team = (TempTeam) redisService.get("temp_team_" + id);
4        if (team == null) {
5            throw new BusinessException(ErrorCode.NOT_FOUND);
6        }
7
8        SseEmitter emitter = new SseEmitter();
9        tempTeamService.addEmitter(id, emitter);
10

简单总结

大概就是这样,在经过网络安全评测之后我们也终于迁入了校内,有了统一的身份管理和鉴权措施,另外管理端也从无到有...希望这个项目能继续用下去吧,不出问题就是最大的幸运了,短时间写 crud 感觉要吐了

COPYRIGHT
作者grtsinry43
版权年份© 2025
许可协议

登山节微信网页实现思路简述 | 实现定位和数据处理》采用知识共享署名 4.0 国际许可协议

转载请注明出处并遵循 CC BY 许可协议条款

相关推荐

使用 pf4j-spring 实现插件注入和 api 接口动态注册 | 插件系统构建(上)

使用 pf4j-spring 实现插件注入和 api 接口动态注册 | 插件系统构建(上)

哪个男孩不想拥有一个自己的插件系统?(x)话说回来,这个我已经计划好久了,不过一直在学其他的东西,刚...

grtsinry43
1/26/2025
511
0
5
使用 BeautifulSoup 配合请求库实现简单的爬虫程序

使用 BeautifulSoup 配合请求库实现简单的爬虫程序

事情起源于课内的课程实验作业...因为要求要用爬虫,~~不必说课内讲的一言难尽,更不必说就算讲了我也...

grtsinry43
1/7/2025
170
0
1
快速搭建专属域名邮箱服务,简单整合邮箱消息推送功能至 Spring Boot 应用程序

快速搭建专属域名邮箱服务,简单整合邮箱消息推送功能至 Spring Boot 应用程序

作为即时通信的重要方式,邮件在互联网互动中起到举足轻重的作用,而搭建邮件推送服务不仅可以做到实时的消...

grtsinry43
1/2/2025
295
2
0

使用Spring Security快速实现Oauth2客户端配置

在Spring的强大生态中,我们很多时候只需要简单的配置就可以完成想要的需求。在Grtblog中,我...

grtsinry43
12/15/2024
375
0
0

Redux 数据仓库的数据持久化和 Next.js 中使用流程 | JustPure 起始页的构建历程

# Redux 数据仓库的数据持久化和 Next.js 中使用流程 | JustPure 起始页的构...

grtsinry43
3/7/2025
144
7
3
COMMENT 7309484506786959360

发表评论

来这里畅所欲言吧!
支持 Markdown 语法 0 / 3000

网站运行时间

0
0
0
0

在风雨飘摇之中

感谢陪伴与支持

愿我们不负热爱,继续前行