@ -0,0 +1,31 @@ |
|||
package com.bx.implatform.annotation; |
|||
|
|||
import java.lang.annotation.*; |
|||
import java.util.concurrent.TimeUnit; |
|||
|
|||
/** |
|||
* 防止表单重复提交注解 |
|||
*/ |
|||
@Inherited |
|||
@Target(ElementType.METHOD) |
|||
@Retention(RetentionPolicy.RUNTIME) |
|||
@Documented |
|||
public @interface RepeatSubmit { |
|||
|
|||
|
|||
/** |
|||
* 间隔时间,小于此时间视为重复提交 |
|||
*/ |
|||
int interval() default 5000; |
|||
|
|||
/** |
|||
* 间隔时间单位 |
|||
*/ |
|||
TimeUnit timeUnit() default TimeUnit.MILLISECONDS; |
|||
|
|||
/** |
|||
* 提示消息 |
|||
*/ |
|||
String message() default "请勿提交重复请求"; |
|||
|
|||
} |
|||
@ -0,0 +1,100 @@ |
|||
package com.bx.implatform.aspect; |
|||
|
|||
import cn.hutool.core.util.ArrayUtil; |
|||
import cn.hutool.core.util.ObjectUtil; |
|||
import cn.hutool.core.util.StrUtil; |
|||
import cn.hutool.crypto.SecureUtil; |
|||
import com.alibaba.fastjson.JSON; |
|||
import com.bx.implatform.annotation.RepeatSubmit; |
|||
import com.bx.implatform.contant.RedisKey; |
|||
import com.bx.implatform.exception.GlobalException; |
|||
import com.bx.implatform.session.SessionContext; |
|||
import jakarta.servlet.http.HttpServletRequest; |
|||
import jakarta.servlet.http.HttpServletResponse; |
|||
import lombok.AllArgsConstructor; |
|||
import org.aspectj.lang.JoinPoint; |
|||
import org.aspectj.lang.annotation.Aspect; |
|||
import org.aspectj.lang.annotation.Before; |
|||
import org.springframework.data.redis.core.RedisTemplate; |
|||
import org.springframework.stereotype.Component; |
|||
import org.springframework.web.context.request.RequestContextHolder; |
|||
import org.springframework.web.context.request.ServletRequestAttributes; |
|||
import org.springframework.web.multipart.MultipartFile; |
|||
|
|||
import java.util.Collection; |
|||
import java.util.Map; |
|||
import java.util.StringJoiner; |
|||
|
|||
/** |
|||
* @author: blue |
|||
* @date: 2024-12-08 |
|||
* @version: 1.0 |
|||
*/ |
|||
|
|||
@Aspect |
|||
@Component |
|||
@AllArgsConstructor |
|||
public class RepeatSubmitAspect { |
|||
|
|||
private final RedisTemplate<String, Object> redisTemplate; |
|||
|
|||
@Before("@annotation(repeatSubmit)") |
|||
public void doBefore(JoinPoint point, RepeatSubmit repeatSubmit) throws Throwable { |
|||
// 如果注解不为0 则使用注解数值
|
|||
long interval = repeatSubmit.timeUnit().toMillis(repeatSubmit.interval()); |
|||
HttpServletRequest request = |
|||
((ServletRequestAttributes)RequestContextHolder.getRequestAttributes()).getRequest(); |
|||
String url = request.getRequestURL().toString(); |
|||
Long userId = SessionContext.getSession().getUserId(); |
|||
String reqParams = argsArrayToString(point.getArgs()); |
|||
String md5 = SecureUtil.md5(StrUtil.join(":", userId, url, reqParams)); |
|||
// 唯一标识
|
|||
String key = String.join(":",RedisKey.IM_REPEAT_SUBMIT,md5) ; |
|||
if(redisTemplate.hasKey(key)){ |
|||
throw new GlobalException(repeatSubmit.message()); |
|||
} |
|||
redisTemplate.opsForValue().set(key,1,repeatSubmit.interval(),repeatSubmit.timeUnit()); |
|||
} |
|||
|
|||
|
|||
/** |
|||
* 参数拼装 |
|||
*/ |
|||
private String argsArrayToString(Object[] paramsArray) { |
|||
StringJoiner params = new StringJoiner(" "); |
|||
if (ArrayUtil.isEmpty(paramsArray)) { |
|||
return params.toString(); |
|||
} |
|||
for (Object o : paramsArray) { |
|||
if (ObjectUtil.isNotNull(o) && !isFilterObject(o)) { |
|||
params.add(JSON.toJSONString(o)); |
|||
} |
|||
} |
|||
return params.toString(); |
|||
} |
|||
|
|||
/** |
|||
* 判断是否需要过滤的对象。 |
|||
* |
|||
* @param o 对象信息。 |
|||
* @return 如果是需要过滤的对象,则返回true;否则返回false。 |
|||
*/ |
|||
@SuppressWarnings("rawtypes") |
|||
public boolean isFilterObject(final Object o) { |
|||
Class<?> clazz = o.getClass(); |
|||
if (clazz.isArray()) { |
|||
return clazz.getComponentType().isAssignableFrom(MultipartFile.class); |
|||
} else if (Collection.class.isAssignableFrom(clazz)) { |
|||
Collection collection = (Collection)o; |
|||
for (Object value : collection) { |
|||
return value instanceof MultipartFile; |
|||
} |
|||
} else if (Map.class.isAssignableFrom(clazz)) { |
|||
Map map = (Map)o; |
|||
for (Object value : map.values()) { |
|||
return value instanceof MultipartFile; |
|||
} |
|||
} |
|||
return o instanceof MultipartFile || o instanceof HttpServletRequest || o instanceof HttpServletResponse; |
|||
} |
|||
} |
|||
@ -1,60 +1,73 @@ |
|||
import Recorder from 'js-audio-recorder'; |
|||
import UNI_APP from '@/.env.js'; |
|||
|
|||
let rc = null; |
|||
let duration = 0; |
|||
let chunks = []; |
|||
let stream = null; |
|||
let start = () => { |
|||
if (rc != null) { |
|||
close(); |
|||
} |
|||
rc = new Recorder(); |
|||
return rc.start(); |
|||
} |
|||
return navigator.mediaDevices.getUserMedia({ audio: true }).then(audioStream => { |
|||
const startTime = new Date().getTime(); |
|||
chunks = []; |
|||
stream = audioStream; |
|||
rc = new MediaRecorder(stream) |
|||
rc.ondataavailable = (e) => { |
|||
console.log("ondataavailable") |
|||
chunks.push(e.data) |
|||
} |
|||
rc.onstop = () => { |
|||
duration = (new Date().getTime() - startTime) / 1000; |
|||
console.log("时长:", duration) |
|||
} |
|||
rc.start() |
|||
}) |
|||
|
|||
let pause = () => { |
|||
rc.pause(); |
|||
} |
|||
|
|||
let close = () => { |
|||
rc.destroy(); |
|||
rc = null; |
|||
stream.getTracks().forEach((track) => { |
|||
track.stop() |
|||
}) |
|||
rc.stop() |
|||
} |
|||
|
|||
|
|||
let upload = () => { |
|||
return new Promise((resolve, reject) => { |
|||
const wavBlob = rc.getWAVBlob(); |
|||
const newbolb = new Blob([wavBlob], { type: 'audio/wav' }) |
|||
const name = new Date().getDate() + '.wav'; |
|||
const file = new File([newbolb], name) |
|||
uni.uploadFile({ |
|||
url: UNI_APP.BASE_URL + '/file/upload', |
|||
header: { |
|||
accessToken: uni.getStorageSync("loginInfo").accessToken |
|||
}, |
|||
file: file, |
|||
name: 'file', |
|||
success: (res) => { |
|||
let r = JSON.parse(res.data); |
|||
if (r.code != 200) { |
|||
console.log(res) |
|||
reject(r.message); |
|||
} else { |
|||
const data = { |
|||
duration: parseInt(rc.duration), |
|||
url: r.data |
|||
setTimeout(() => { |
|||
const newbolb = new Blob(chunks, { 'type': 'audio/mpeg' }); |
|||
const name = new Date().getDate() + '.mp3'; |
|||
const file = new File([newbolb], name) |
|||
console.log("upload") |
|||
uni.uploadFile({ |
|||
url: UNI_APP.BASE_URL + '/file/upload', |
|||
header: { |
|||
accessToken: uni.getStorageSync("loginInfo").accessToken |
|||
}, |
|||
file: file, |
|||
name: 'file', |
|||
success: (res) => { |
|||
let r = JSON.parse(res.data); |
|||
if (r.code != 200) { |
|||
console.log(res) |
|||
reject(r.message); |
|||
} else { |
|||
const data = { |
|||
duration: parseInt(duration), |
|||
url: r.data |
|||
} |
|||
resolve(data); |
|||
} |
|||
resolve(data); |
|||
}, |
|||
fail: (e) => { |
|||
reject(e); |
|||
} |
|||
}, |
|||
fail: (e) => { |
|||
reject(e); |
|||
} |
|||
}) |
|||
}) |
|||
}, 100) |
|||
}) |
|||
} |
|||
|
|||
export { |
|||
start, |
|||
pause, |
|||
close, |
|||
upload |
|||
} |
|||
|
Before Width: | Height: | Size: 813 KiB |
|
After Width: | Height: | Size: 1.0 MiB |
|
Before Width: | Height: | Size: 767 KiB |
|
After Width: | Height: | Size: 1.5 MiB |
|
After Width: | Height: | Size: 461 KiB |
|
After Width: | Height: | Size: 1.6 MiB |
|
After Width: | Height: | Size: 279 KiB |
|
Before Width: | Height: | Size: 367 KiB |
|
After Width: | Height: | Size: 460 KiB |
|
Before Width: | Height: | Size: 629 KiB |
|
After Width: | Height: | Size: 452 KiB |
|
Before Width: | Height: | Size: 795 KiB |
|
After Width: | Height: | Size: 205 KiB |
|
Before Width: | Height: | Size: 612 KiB |
|
Before Width: | Height: | Size: 3.4 MiB |