zoujiajian пре 2 година
родитељ
комит
fbde17ae2b

+ 313 - 0
netflix-common/src/main/java/com/cyksj/common/util/Times.java

@@ -0,0 +1,313 @@
+package com.cyksj.common.util;
+
+import cn.hutool.core.date.DateUtil;
+
+import java.time.Duration;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.ZoneOffset;
+import java.time.format.DateTimeFormatter;
+import java.time.temporal.ChronoUnit;
+import java.time.temporal.TemporalAdjusters;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.Locale;
+
+/**
+ * @author valor
+ * @date 2018/9/22 14:17
+ */
+public class Times {
+
+    /**
+     * 一天的秒数
+     */
+    public static final int second_per_day = 86400;
+    /**
+     * 一小时的毫秒数
+     */
+    public static final int millisecond_per_hour = 3600000;
+
+    /**
+     * 一个月的秒数
+     */
+    public static final int second_per_month = 259200;
+
+    /**
+     * 一分钟的毫秒数
+     */
+    public static final int millisecond_per_minute = 60000;
+
+    /**
+     * 默认时区
+     */
+//    private static final ZoneId zoneId;
+//    static {
+//        zoneId = ZoneId.of("Asia/Shanghai");
+//    }
+
+    /**
+     * 默认时区的时间偏移量
+     */
+    private static final ZoneOffset zoneOffSet;
+    static {
+        // 8小时的秒数 8 * 60 * 60
+        zoneOffSet = ZoneOffset.ofTotalSeconds(28800);
+    }
+
+    /**
+     * 时间格式化 枚举类
+     */
+    public enum Formatter {
+        DateTime(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss", Locale.US)),
+        ISO_8601(DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.US)),
+        RFC_1123(DateTimeFormatter.ofPattern("EEE, dd MMM yyyy HH:mm:ss 'GMT'", Locale.US)),
+        DATAS(DateTimeFormatter.ofPattern("yyyy-MM-dd",Locale.US)),
+        DATA_YM(DateTimeFormatter.ofPattern("yyyy-MM",Locale.US)),
+        DATAS_ST(DateTimeFormatter.ofPattern("yyyy-MM-10",Locale.US)),
+        MSEC(DateTimeFormatter.ofPattern("yyyy-MM-dd HHmmssSSS",Locale.US)),
+        HOUR_MIN_SEC(DateTimeFormatter.ofPattern("HH:mm:ss",Locale.US)),
+        HOUR_MIN_SEC_MSEC(DateTimeFormatter.ofPattern("HH:mm:ss:SSS",Locale.US)),
+        YEAR_MONTH_DAY(DateTimeFormatter.ofPattern("yyyyMMdd HH:mm:ss",Locale.US)),
+        DATE_YMD(DateTimeFormatter.ofPattern("yyyyMMdd",Locale.US)),
+        DATA_10H(DateTimeFormatter.ofPattern("yyyy-MM-dd 10:00:00", Locale.US)),
+        MONTH_DAY(DateTimeFormatter.ofPattern("MM/dd",Locale.US));
+
+
+        private DateTimeFormatter formatter;
+
+        Formatter(DateTimeFormatter formatter) {
+            this.formatter = formatter;
+        }
+
+        public DateTimeFormatter getFormatter() {
+            return this.formatter;
+        }
+    }
+
+    /**
+     * 不允许外部实例化
+     */
+    private Times() { }
+
+    /**
+     * 指定日期字符串 按指定格式转换成timestamp
+     */
+    public static long toTimestamp(String str, Formatter formatter) {
+        return toTimestamp(str, formatter, zoneOffSet);
+    }
+
+    /**
+     * 指定日期字符串 按指定格式转换成timestamp
+     */
+    public static long toTimestamp(String str, Formatter formatter, ZoneOffset offset) {
+        return toDateTime(str, formatter).toEpochSecond(offset) * 1000L;
+    }
+
+    /**
+     * 指定timestamp 按指定格式 格式化时间
+     */
+    public static String format(long time, Formatter formatter) {
+        return format(time, formatter, zoneOffSet);
+    }
+
+    /**
+     * 指定timestamp 按指定格式 格式化时间
+     */
+    public static String format(long time, Formatter formatter, ZoneOffset offset) {
+        return format(toDateTime(time, offset), formatter);
+    }
+
+    /**
+     * 指定LocalDateTime 按指定格式 格式化时间
+     */
+    public static String format(LocalDateTime time, Formatter formatter) {
+        return time.format(formatter.getFormatter());
+    }
+
+    /**
+     * 指定日期字符串 按指定格式转换成localDateTime
+     */
+    public static LocalDateTime toDateTime(String str, Formatter formatter) {
+        return LocalDateTime.parse(str, formatter.getFormatter());
+    }
+
+    /**
+     * 指定timestamp 转换成localDateTime
+     */
+    public static LocalDateTime toDateTime(long timestamp) {
+        return toDateTime(timestamp, zoneOffSet);
+    }
+
+    /**
+     * 指定timestamp 转换成localDateTime
+     */
+    public static LocalDateTime toDateTime(long timestamp, ZoneOffset offset) {
+        long secs = timestamp / 1000L;
+        return LocalDateTime.ofEpochSecond(secs, 0, offset);
+    }
+
+    /**
+     * 判断指定timestamp 是否是过去的时间
+     */
+    public static boolean isPast(long time) {
+        return System.currentTimeMillis() > time;
+    }
+
+    /**
+     * 判断指定timestamp 是否是未来的时间
+     */
+    public static boolean isFuture(long time) {
+        return System.currentTimeMillis() < time;
+    }
+
+    /**
+     * 判断当前timestamp 是否在某个时间段内
+     */
+    public static boolean isInTime(long start, long end) {
+        return isInTime(System.currentTimeMillis(), start, end);
+    }
+
+    /**
+     * 判断指定timestamp 是否在某个时间段内
+     */
+    public static boolean isInTime(long time, long start, long end) {
+        return start <= time && time <= end;
+    }
+
+    /**
+     * 计算两个日期相差的 毫秒数
+     */
+    public static long between(LocalDateTime one, LocalDateTime another) {
+        return Duration.between(one, another).toMillis();
+    }
+
+    /**
+     * 获取今日零时timestamp
+     */
+    public static long getZeroPoint() {
+        return getZeroPoint(0);
+    }
+
+    /**
+     * 获取今日日期 格式 yyyy-MM-dd
+     */
+    public static String getTodayStr(){
+        return format(getZeroPoint(0),Formatter.DATAS);
+    }
+
+    /**
+     * 指定与今日相差的天数 获取零时timestamp
+     */
+    public static long getZeroPoint(int days) {
+        return getZeroPoint(days, zoneOffSet);
+    }
+
+    /**
+     * 指定与今日相差的天数 获取零时timestamp
+     */
+    public static long getZeroPoint(int days, ZoneOffset zoneOffSet) {
+        return getZeroPoint(System.currentTimeMillis(), days, zoneOffSet);
+    }
+
+    /**
+     * 指定时间戳 获取零时timestamp
+     */
+    public static long getZeroPoint(long timestamp) {
+        return getZeroPoint(timestamp, 0);
+    }
+
+    /**
+     * 指定时间戳 获取零时timestamp
+     */
+    public static long getZeroPoint(long timestamp, int days) {
+        return getZeroPoint(timestamp, days, zoneOffSet);
+    }
+
+    /**
+     * 指定时间戳 获取零时timestamp
+     */
+    public static long getZeroPoint(long timestamp, int days, ZoneOffset zoneOffSet) {
+        int offset = zoneOffSet.getTotalSeconds();
+
+        long time = timestamp / 1000L + offset;
+        long point = time - time % second_per_day;
+
+        if (0 != days) {
+            point += days * second_per_day;
+        }
+
+        // 计算时 传入的时区为标准
+        return (point - offset) * 1000L;
+    }
+
+    /**
+     * 获取当前时间的10号时间戳
+     */
+    public static long getStartTime(long startTime){
+        Calendar c = Calendar.getInstance();
+        c.setTimeInMillis(startTime);
+        long l = Times.toTimestamp(Times.format(c.getTimeInMillis(),Times.Formatter.DATAS_ST) + " 00:00:00", Times.Formatter.DateTime);
+        if (c.getTimeInMillis() < l){
+            c.add(Calendar.MONTH, -1);
+            l = Times.toTimestamp(Times.format(c.getTimeInMillis(),Times.Formatter.DATAS_ST) + " 00:00:00", Times.Formatter.DateTime);
+        }
+        return l;
+    }
+    /**
+     * 获取当前时间的次月10号时间戳
+     */
+    public static long getEndTime(){
+        Calendar c = Calendar.getInstance();
+        long l = Times.toTimestamp(Times.format(c.getTimeInMillis(), Times.Formatter.DATAS_ST) + " 00:00:00", Times.Formatter.DateTime);
+        if (c.getTimeInMillis() >= l){
+            c.add(Calendar.MONTH, 1);
+            l = Times.toTimestamp(Times.format(c.getTimeInMillis(), Times.Formatter.DATAS_ST) + " 00:00:00", Times.Formatter.DateTime);
+        }
+        return l;
+    }
+
+    /**
+     * 判断选择的日期是否是本周
+     */
+    public static boolean isThisWeek(long time) {
+        Calendar calendar = Calendar.getInstance();
+        calendar.setFirstDayOfWeek(Calendar.MONDAY);
+        int currentWeek = calendar.get(Calendar.WEEK_OF_YEAR);
+        calendar.setTime(new Date(time));
+        int paramWeek = calendar.get(Calendar.WEEK_OF_YEAR);
+        return paramWeek == currentWeek;
+    }
+
+
+    /**
+     * 传入一个日期返回距今几年几天
+     * @param date
+     * @return 1年223天
+     */
+    public static String getDaysOfYear(String date) {
+        String time = "";
+        LocalDate startDate = LocalDate.parse(date);
+        startDate = startDate.plusDays(-1L);
+        LocalDate endDate = LocalDate.parse(DateUtil.format(new Date(), "yyyy-MM-dd"));
+        LocalDate startDateOfYear = startDate.with(TemporalAdjusters.firstDayOfYear());
+        LocalDate endDateOfYear = endDate.with(TemporalAdjusters.firstDayOfYear());
+        long day1 = ChronoUnit.DAYS.between(startDate, endDate);
+        long year = ChronoUnit.YEARS.between(startDate, endDate);
+        long day2 = ChronoUnit.DAYS.between(startDateOfYear, endDateOfYear);
+        if (day2 > day1) {
+            endDateOfYear = endDateOfYear.minusYears(1);
+            day2 = ChronoUnit.DAYS.between(startDateOfYear, endDateOfYear);
+        }
+        long sub = day1 - day2;
+        if (year > 0) {
+            time = year + "年";
+            if (sub > 0) {
+                time = time + sub + "天";
+            }
+        } else if (sub > 0) {
+            time = sub + "天";
+        }
+        return time;
+    }
+}

+ 18 - 0
netflix-dao/src/main/java/com/cyksj/model/request/MiniSchemaReq.java

@@ -0,0 +1,18 @@
+package com.cyksj.model.request;
+
+import lombok.Getter;
+import lombok.Setter;
+
+/**
+ * 项目名: yhlxj2
+ * 文件名: MiniSchemaReq
+ * 创建者: JavaZou
+ * 创建时间:2024/5/6 16:12
+ */
+@Getter
+@Setter
+public class MiniSchemaReq {
+	private String dsCode;
+
+	private String path;
+}

+ 76 - 0
netflix-dao/src/main/java/com/cyksj/model/request/TargetAppletSchemeReq.java

@@ -0,0 +1,76 @@
+package com.cyksj.model.request;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+
+/*
+ *项目名: yicang-server
+ *文件名: JiShanSchemeReq
+ *创建者: JavaZou
+ *创建时间:2022/8/22 13:56
+ */
+@Getter
+@Setter
+public class TargetAppletSchemeReq {
+	/**
+	 * 跳转到的目标小程序信息
+	 */
+	@JsonProperty("jump_wxa")
+	private JumpWxa jumpWxa;
+	/**
+	 * 到期失效的 scheme 码失效类型,失效时间:0,失效间隔天数:1
+	 */
+	@JsonProperty("expire_type")
+	private Long expireType;
+	/**
+	 * 到期失效的 scheme 码的失效时间,为 Unix 时间戳。
+	 * 生成的到期失效 scheme 码在该时间前有效。
+	 * 最长有效期为30天。expire_type 为 0 时必填
+	 */
+	@JsonProperty("expire_time")
+	private Long expireTime;
+	/**
+	 * 到期失效的 scheme 码的失效间隔天数。
+	 * 生成的到期失效 scheme 码在该间隔时间到达前有效。最长间隔天数为30天。
+	 * expire_type 为 1 时必填
+	 */
+	@JsonProperty("expire_interval")
+	private Long expireInterval;
+
+	/**
+	 * 默认值false。
+	 * 生成的 scheme 码类型,到期失效:true,永久有效:false。注意,
+	 * 永久有效 scheme 和有效时间超过180天的到期失效 scheme 的总数上限为10万个,
+	 */
+	@JsonProperty("is_expire")
+	private Boolean isExpired;
+
+	@Getter
+	@Setter
+	@AllArgsConstructor
+	@NoArgsConstructor
+	public static class JumpWxa {
+		/**
+		 * 通过 scheme 码进入的小程序页面路径,必须是已经发布的小程序存在的页面,不可携带 query。path 为空时会跳转小程序主页。
+		 */
+		private String path;
+		/**
+		 * 通过 scheme 码进入小程序时的 query,最大1024个字符,只支持数字,大小写英文以及部分特殊字符:`!#$&'()*+,/:;=?@-._~%``
+		 */
+		private String query;
+		/**
+		 * 要打开的小程序版本。
+		 * 正式版为"release",体验版为"trial",开发版为"develop",仅在微信外打开时生效。
+		 */
+		@JsonProperty("env_version")
+		private String envVersion;
+
+		public JumpWxa(String path, String query) {
+			this.path = path;
+			this.query = query;
+		}
+	}
+}

+ 1 - 0
netflix-service/src/main/java/com/cyksj/redis/RedisService.java

@@ -831,6 +831,7 @@ public class RedisService {
         CHATGPT_CAR_SCORES("chatgpt:car:scores","chatGpt 车队分数", 48 * 60 * 60L),
         CHATGPT_CAR_HIGH_CHAT("chatgpt:car:high:chat:","chatGpt gpt4 对话次数", 48 * 60 * 60L),
         CHATGPT_CAR_LOW_CHAT("chatgpt:car:low:chat:","chatGpt 3.5 对话次数", 48 * 60 * 60L),
+        MINI_SCHEMA_URL_PATH("mini_schema_url_path:", "小程序schema", 23 * 60 * 60L),
 
         ;
 

+ 6 - 0
netflix-service/src/main/java/com/cyksj/service/mini/MiniService.java

@@ -3,6 +3,7 @@ package com.cyksj.service.mini;
 import com.cyksj.dto.Result;
 import com.cyksj.model.entity.User;
 import com.cyksj.model.request.MiniArticleReq;
+import com.cyksj.model.request.MiniSchemaReq;
 import com.cyksj.model.views.ArticleDetailView;
 
 /*
@@ -21,4 +22,9 @@ public interface MiniService {
 	ArticleDetailView getArticleDetailView(Long articleId, Long userId);
 
 	void getStudentIdentity(User user, String code) throws Exception;
+
+	/**
+	 * 生成小程序schema
+	 */
+	String generateSchema(MiniSchemaReq req) throws Exception;
 }

+ 37 - 4
netflix-service/src/main/java/com/cyksj/service/mini/impl/MiniServiceImpl.java

@@ -1,8 +1,10 @@
 package com.cyksj.service.mini.impl;
 
 import cn.hutool.core.util.RandomUtil;
+import cn.hutool.core.util.StrUtil;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import com.cyksj.common.util.Times;
 import com.cyksj.config.YhCertMiniConfig;
 import com.cyksj.config.YhMiniConfig;
 import com.cyksj.dto.Result;
@@ -15,6 +17,8 @@ import com.cyksj.model.entity.Article;
 import com.cyksj.model.entity.ArticleUserMark;
 import com.cyksj.model.entity.User;
 import com.cyksj.model.request.MiniArticleReq;
+import com.cyksj.model.request.MiniSchemaReq;
+import com.cyksj.model.request.TargetAppletSchemeReq;
 import com.cyksj.model.views.ArticleDetailView;
 import com.cyksj.model.wechat.MiniURLLink;
 import com.cyksj.redis.RedisService;
@@ -42,16 +46,16 @@ public class MiniServiceImpl implements MiniService {
 
 	private final YhMiniConfig yhMiniConfig;
 
-	private final YhCertMiniConfig certMiniConfig;
-
-	private final RedisService redisService;
-
 	private final UserMapper userMapper;
 
 	private final ArticleUserMarkMapper articleUserMarkMapper;
 
 	private final ArticleMapper articleMapper;
 
+	private final YhCertMiniConfig certMiniConfig;
+
+	private final RedisService redisService;
+
 	@Override
 	public String getMiniURLLink(String path, String query) throws Exception {
 		MiniURLLink miniURLLink = new MiniURLLink();
@@ -161,6 +165,35 @@ public class MiniServiceImpl implements MiniService {
 		weChatService.getMiniStudentIdentity(user.getOpenId(), code);
 	}
 
+	@Override
+	public String generateSchema(MiniSchemaReq req) throws Exception {
+		//跳转路径
+		req.setPath("pages/index/index");
+		String key = RedisService.key.MINI_SCHEMA_URL_PATH.getEnvName() + req.getPath();
+		String miniSchema = redisService.getStr(key);
+		if (StrUtil.isEmpty(miniSchema)) {
+			try {
+				//调用微信小程序接口
+				String accessToken = weChatService.getAccessToken(certMiniConfig.getAppId());
+				TargetAppletSchemeReq targetAppletSchemeReq = new TargetAppletSchemeReq();
+				targetAppletSchemeReq.setExpireType(0l);
+				targetAppletSchemeReq.setExpireTime((System.currentTimeMillis() / 1000) + Times.second_per_day);
+				targetAppletSchemeReq.setIsExpired(false);
+				TargetAppletSchemeReq.JumpWxa jumpWxa = new TargetAppletSchemeReq.JumpWxa(req.getPath(), "");
+				targetAppletSchemeReq.setJumpWxa(jumpWxa);
+				miniSchema = weChatService.generateSchema(accessToken, targetAppletSchemeReq);
+				if (StrUtil.isNotBlank(miniSchema)) {
+					redisService.setNx(key, miniSchema, RedisService.key.MINI_SCHEMA_URL_PATH.getTimeout());
+				}
+			} catch (Exception e) {
+			}
+		}
+		if (StrUtil.isNotBlank(req.getDsCode()) && StrUtil.isNotBlank(miniSchema)) {
+			miniSchema = miniSchema.contains("?") ? miniSchema + "&dsCode=" + req.getDsCode() : miniSchema + "?dsCode=" + req.getDsCode();
+		}
+		return miniSchema;
+	}
+
 	public void updateArticleMarkNum(Integer type, Long articleId, Integer offset) {
 		Integer success = 0;
 		while (success != 1) {

+ 3 - 0
netflix-service/src/main/java/com/cyksj/service/wechat/WeChatService.java

@@ -2,6 +2,7 @@ package com.cyksj.service.wechat;
 
 
 import com.cyksj.model.dto.*;
+import com.cyksj.model.request.TargetAppletSchemeReq;
 import com.cyksj.model.response.WxGzhQrCodeTicketRep;
 import com.cyksj.model.wechat.MiniURLLink;
 
@@ -103,4 +104,6 @@ public interface WeChatService {
     MiniProgramSession code2Session(String appId, String code) throws Exception;
 
     void getMiniStudentIdentity(String openId, String code) throws Exception;
+
+	String generateSchema(String accessToken, TargetAppletSchemeReq targetAppletSchemeReq) throws Exception;
 }

+ 23 - 0
netflix-service/src/main/java/com/cyksj/service/wechat/impl/WeChatServiceImpl.java

@@ -23,6 +23,7 @@ import com.cyksj.model.dto.*;
 import com.cyksj.model.entity.ShopConfig;
 import com.cyksj.model.entity.SysConfig;
 import com.cyksj.model.entity.WxApp;
+import com.cyksj.model.request.TargetAppletSchemeReq;
 import com.cyksj.model.response.WxGzhQrCodeTicketRep;
 import com.cyksj.model.wechat.MiniURLLink;
 import com.cyksj.redis.RedisService;
@@ -696,4 +697,26 @@ public class WeChatServiceImpl implements WeChatService {
             throw BusinessRuntimeException.getInstance("大学生身份审核中");
         }
     }
+
+    @Override
+    public String generateSchema(String accessToken, TargetAppletSchemeReq targetAppletSchemeReq) throws Exception {
+        String url = "https://api.weixin.qq.com/wxa/generatescheme?access_token=" + accessToken;
+        HttpResponse<byte[]> res = J11HttpC.custom()
+                .ofPost()
+                .url(url)
+                .body(HttpRequest.BodyPublishers.ofString(Jsons.toJson(targetAppletSchemeReq)))
+                .send(HttpResponse.BodyHandlers.ofByteArray());
+        if (200 != res.statusCode()) {
+            throw BusinessRuntimeException.getInstance("获取用户信息失败: " + IoKit.toString(res.body()));
+        }
+
+        JSONObject jsonObject = Jsons.parseObject(res.body(), JSONObject.class);
+        if (jsonObject.getInt("errcode") != 0) {
+            throw BusinessRuntimeException.getInstance(jsonObject.getStr("errmsg"));
+        }
+        if (StrUtil.isEmpty(jsonObject.getStr("openlink"))) {
+            log.info("积善scheme body: {}", jsonObject);
+        }
+        return jsonObject.getStr("openlink");
+    }
 }

+ 12 - 0
netflix-web/src/main/java/com/cyksj/web/controller/mini/MiniController.java

@@ -6,6 +6,7 @@ import com.cyksj.enums.GatewayResponse;
 import com.cyksj.model.entity.User;
 import com.cyksj.model.request.MiniArticleReq;
 import com.cyksj.model.request.MiniProgramLoginPost;
+import com.cyksj.model.request.MiniSchemaReq;
 import com.cyksj.model.views.ArticleDetailView;
 import com.cyksj.model.views.ArticleFrontView;
 import com.cyksj.model.views.ArticleSelfFrontView;
@@ -49,6 +50,17 @@ public class MiniController {
 		return GatewayResponse.SUCCESS.newBuilder().toResult(linkUrl);
 	}
 
+	/**
+	 * 生成小程序schema
+	 */
+	@GetMapping("/generate/schema")
+	public Result<String> generateSchema(String dsCode) throws Exception {
+		MiniSchemaReq req = new MiniSchemaReq();
+		req.setDsCode(dsCode);
+		String url = miniService.generateSchema(req);
+		return GatewayResponse.SUCCESS.newBuilder().toResult(url);
+	}
+
 	/**
 	 * 银河小程序 静默授权登录
 	 */