forked from dyf/fys-Multi-tenant
Compare commits
28 Commits
a109f187b9
...
main
Author | SHA1 | Date | |
---|---|---|---|
7d91426414 | |||
ad06811747 | |||
aeaa906bc9 | |||
36092932bf | |||
34188f20dd | |||
0fa0e4ab1b | |||
e321dcd652 | |||
78e2538f71 | |||
faef79e56d | |||
7b1615ce4d | |||
171eeabea1 | |||
cc776300ec | |||
04df7e3ec7 | |||
7be94fe1d2 | |||
6f24759361 | |||
530ee83488 | |||
f938716e2d | |||
bbf1c6cfd5 | |||
082f890009 | |||
800b825892 | |||
cfafbc54f7 | |||
fdb64b1dcc | |||
fbbe90207e | |||
2f38f08538 | |||
eb1552d982 | |||
1eb2502a21 | |||
2815e27240 | |||
f119dd158b |
@ -29,16 +29,16 @@ public class AppOperationVideoController extends BaseController {
|
||||
* 查询操作视频列表
|
||||
*/
|
||||
@GetMapping("/listOperationVideos")
|
||||
public List<AppOperationVideoVo> listOperationVideos(AppOperationVideoBo bo) {
|
||||
return appOperationVideoService.queryList(bo);
|
||||
public R<List<AppOperationVideoVo>> listOperationVideos(AppOperationVideoBo bo) {
|
||||
return R.ok(appOperationVideoService.queryList(bo));
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询操作视频详情信息
|
||||
*/
|
||||
@GetMapping("/getOperationVideoInfo")
|
||||
public AppOperationVideoVo getOperationVideoInfo(AppOperationVideoBo bo) {
|
||||
return appOperationVideoService.queryById(bo.getId());
|
||||
public R<AppOperationVideoVo> getOperationVideoInfo(AppOperationVideoBo bo) {
|
||||
return R.ok(appOperationVideoService.queryById(bo.getId()));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -64,8 +64,8 @@ public class AppOperationVideoController extends BaseController {
|
||||
/**
|
||||
* 操作视频删除
|
||||
*/
|
||||
@DeleteMapping("/deleteOperationVideo")
|
||||
public R<Void> deleteOperationVideo(Long[] ids) {
|
||||
return toAjax(appOperationVideoService.deleteWithValidByIds(List.of(ids), true));
|
||||
@DeleteMapping("/deleteOperationVideo/{id}")
|
||||
public R<Void> deleteOperationVideo(@PathVariable Long id) {
|
||||
return toAjax(appOperationVideoService.deleteWithValidByIds(List.of(id), true));
|
||||
}
|
||||
}
|
||||
|
@ -22,4 +22,5 @@ public class AppSmsLoginBody {
|
||||
*/
|
||||
@NotBlank(message = "租户ID不能为空")
|
||||
private String tenantId;
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,102 @@
|
||||
package com.fuyuanshen.mp.controller;
|
||||
|
||||
import cn.dev33.satoken.annotation.SaIgnore;
|
||||
import cn.dev33.satoken.exception.NotLoginException;
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import com.fuyuanshen.app.model.AppSmsLoginBody;
|
||||
import com.fuyuanshen.app.service.AppLoginService;
|
||||
import com.fuyuanshen.common.core.constant.SystemConstants;
|
||||
import com.fuyuanshen.common.core.domain.R;
|
||||
import com.fuyuanshen.common.core.domain.model.AppLoginUser;
|
||||
import com.fuyuanshen.common.core.domain.model.LoginUser;
|
||||
import com.fuyuanshen.common.core.domain.model.RegisterBody;
|
||||
import com.fuyuanshen.common.core.domain.model.SmsLoginBody;
|
||||
import com.fuyuanshen.common.core.utils.*;
|
||||
import com.fuyuanshen.common.encrypt.annotation.ApiEncrypt;
|
||||
import com.fuyuanshen.common.json.utils.JsonUtils;
|
||||
import com.fuyuanshen.common.satoken.utils.LoginHelper;
|
||||
import com.fuyuanshen.common.tenant.helper.TenantHelper;
|
||||
import com.fuyuanshen.equipment.domain.UserApp;
|
||||
import com.fuyuanshen.equipment.enums.AppUserTypeEnum;
|
||||
import com.fuyuanshen.mp.domian.dto.AuthUserDto;
|
||||
import com.fuyuanshen.mp.service.MPAuthService;
|
||||
import com.fuyuanshen.mp.service.MPService;
|
||||
import com.fuyuanshen.system.domain.bo.SysTenantBo;
|
||||
import com.fuyuanshen.system.domain.vo.SysClientVo;
|
||||
import com.fuyuanshen.system.domain.vo.SysTenantVo;
|
||||
import com.fuyuanshen.system.domain.vo.SysUserVo;
|
||||
import com.fuyuanshen.system.service.ISysClientService;
|
||||
import com.fuyuanshen.system.service.ISysConfigService;
|
||||
import com.fuyuanshen.system.service.ISysTenantService;
|
||||
import com.fuyuanshen.web.domain.vo.LoginTenantVo;
|
||||
import com.fuyuanshen.web.domain.vo.LoginVo;
|
||||
import com.fuyuanshen.web.domain.vo.TenantListVo;
|
||||
import com.fuyuanshen.web.service.IAuthStrategy;
|
||||
import com.fuyuanshen.web.service.SysRegisterService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.dromara.sms4j.api.SmsBlend;
|
||||
import org.dromara.sms4j.api.entity.SmsResponse;
|
||||
import org.dromara.sms4j.core.factory.SmsFactory;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.net.URL;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* APP认证
|
||||
*
|
||||
* @author Lion Li
|
||||
*/
|
||||
@Slf4j
|
||||
@SaIgnore
|
||||
@RequiredArgsConstructor
|
||||
@RestController
|
||||
@RequestMapping("/mp")
|
||||
public class MPAuthController {
|
||||
|
||||
private final AppLoginService loginService;
|
||||
private final SysRegisterService registerService;
|
||||
private final ISysConfigService configService;
|
||||
private final ISysTenantService tenantService;
|
||||
private final ISysClientService clientService;
|
||||
private final MPAuthService mpAuthService;
|
||||
private final MPService mpService;
|
||||
|
||||
|
||||
@Operation(summary = "小程序登录授权")
|
||||
@PostMapping(value = "/login")
|
||||
public ResponseEntity<Object> login(@RequestBody AuthUserDto authUser, HttpServletRequest request) throws Exception {
|
||||
|
||||
Long phoneNumber = authUser.getPhoneNumber();
|
||||
// 判断小程序用户是否存在,不存在创建
|
||||
UserApp mpUser = mpService.getMpUser(phoneNumber);
|
||||
if (mpUser == null) {
|
||||
RegisterBody registerBody = new RegisterBody();
|
||||
registerBody.setUsername(phoneNumber.toString());
|
||||
registerBody.setUserType(AppUserTypeEnum.XCX_USER.getCode());
|
||||
registerBody.setTenantId("894078");
|
||||
registerBody.setPassword("123456");
|
||||
mpAuthService.register(registerBody);
|
||||
}
|
||||
|
||||
// 去登录
|
||||
SysClientVo client = clientService.queryByClientId("ca839698e245d60aa2f0e59bd52b34f8");
|
||||
UserApp user = mpService.loadUserByUsername(phoneNumber.toString());
|
||||
LoginUser loginUser = mpAuthService.buildLoginUser(user);
|
||||
LoginVo login = mpAuthService.login(loginUser, client);
|
||||
|
||||
// 返回登录信息
|
||||
return ResponseEntity.ok(login);
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,37 @@
|
||||
package com.fuyuanshen.mp.controller;
|
||||
|
||||
import cn.dev33.satoken.annotation.SaIgnore;
|
||||
import com.fuyuanshen.common.core.domain.ResponseVO;
|
||||
import com.fuyuanshen.equipment.service.DeviceService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
/**
|
||||
* @author: 默苍璃
|
||||
* @date: 2025-07-1208:36
|
||||
*/
|
||||
@Slf4j
|
||||
@RestController
|
||||
@RequestMapping("/mp")
|
||||
@RequiredArgsConstructor
|
||||
@Tag(name = "小程序:相关接口")
|
||||
@SaIgnore
|
||||
public class MPController {
|
||||
|
||||
private final DeviceService deviceService;
|
||||
|
||||
|
||||
@GetMapping("/queryDevice")
|
||||
@Operation(summary = "检查是否存在设备MAC号")
|
||||
public ResponseVO<Boolean> queryDevice(@Parameter(name = "设备mac值") String mac) {
|
||||
return ResponseVO.success(deviceService.queryDevice(mac));
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Copyright 2019-2025 Zheng Jie
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.fuyuanshen.mp.domian.dto;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
|
||||
/**
|
||||
* @author Zheng Jie
|
||||
* @date 2018-11-30
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
public class AuthUserDto {
|
||||
|
||||
@Schema(name = "用户名")
|
||||
private String username;
|
||||
|
||||
@Schema(name = "手机号(APP/小程序 登录)")
|
||||
private Long phoneNumber;
|
||||
|
||||
@Schema(name = "密码")
|
||||
private String password;
|
||||
|
||||
@Schema(name = "验证码")
|
||||
private String code;
|
||||
|
||||
@Schema(name = "验证码的key")
|
||||
private String uuid = "";
|
||||
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
package com.fuyuanshen.mp.mapper;
|
||||
|
||||
/**
|
||||
* @author: 默苍璃
|
||||
* @date: 2025-07-1210:16
|
||||
*/
|
||||
public interface MPMapper {
|
||||
}
|
@ -0,0 +1,164 @@
|
||||
package com.fuyuanshen.mp.service;
|
||||
|
||||
import cn.dev33.satoken.exception.NotLoginException;
|
||||
import cn.dev33.satoken.stp.StpUtil;
|
||||
import cn.dev33.satoken.stp.parameter.SaLoginParameter;
|
||||
import cn.hutool.core.bean.BeanUtil;
|
||||
import cn.hutool.core.lang.Opt;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import cn.hutool.crypto.digest.BCrypt;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import com.fuyuanshen.app.domain.vo.AppUserVo;
|
||||
import com.fuyuanshen.app.service.IAppRoleService;
|
||||
import com.fuyuanshen.common.core.constant.Constants;
|
||||
import com.fuyuanshen.common.core.constant.SystemConstants;
|
||||
import com.fuyuanshen.common.core.constant.TenantConstants;
|
||||
import com.fuyuanshen.common.core.domain.dto.PostDTO;
|
||||
import com.fuyuanshen.common.core.domain.dto.RoleDTO;
|
||||
import com.fuyuanshen.common.core.domain.model.AppLoginUser;
|
||||
import com.fuyuanshen.common.core.domain.model.LoginUser;
|
||||
import com.fuyuanshen.common.core.domain.model.PasswordLoginBody;
|
||||
import com.fuyuanshen.common.core.domain.model.RegisterBody;
|
||||
import com.fuyuanshen.common.core.enums.LoginType;
|
||||
import com.fuyuanshen.common.core.enums.UserType;
|
||||
import com.fuyuanshen.common.core.exception.user.UserException;
|
||||
import com.fuyuanshen.common.core.utils.*;
|
||||
import com.fuyuanshen.common.json.utils.JsonUtils;
|
||||
import com.fuyuanshen.common.log.event.LogininforEvent;
|
||||
import com.fuyuanshen.common.redis.utils.RedisUtils;
|
||||
import com.fuyuanshen.common.satoken.utils.AppLoginHelper;
|
||||
import com.fuyuanshen.common.satoken.utils.LoginHelper;
|
||||
import com.fuyuanshen.common.tenant.exception.TenantException;
|
||||
import com.fuyuanshen.common.tenant.helper.TenantHelper;
|
||||
import com.fuyuanshen.equipment.domain.Device;
|
||||
import com.fuyuanshen.equipment.domain.UserApp;
|
||||
import com.fuyuanshen.equipment.service.AppUserService;
|
||||
import com.fuyuanshen.system.domain.SysUser;
|
||||
import com.fuyuanshen.system.domain.bo.SysUserBo;
|
||||
import com.fuyuanshen.system.domain.vo.*;
|
||||
import com.fuyuanshen.system.service.ISysTenantService;
|
||||
import com.fuyuanshen.system.service.ISysUserService;
|
||||
import com.fuyuanshen.web.domain.vo.LoginVo;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.*;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* 登录校验方法
|
||||
*
|
||||
* @author Lion Li
|
||||
*/
|
||||
|
||||
@RequiredArgsConstructor
|
||||
@Slf4j
|
||||
@Service
|
||||
public class MPAuthService {
|
||||
|
||||
private final ISysUserService userService;
|
||||
private final AppUserService appUserService;
|
||||
|
||||
/**
|
||||
* 小程序注册
|
||||
*/
|
||||
public void register(RegisterBody registerBody) {
|
||||
String tenantId = registerBody.getTenantId();
|
||||
String username = registerBody.getUsername();
|
||||
String password = registerBody.getPassword();
|
||||
// 校验用户类型是否存在
|
||||
String userType = UserType.getUserType(registerBody.getUserType()).getUserType();
|
||||
|
||||
UserApp sysUser = new UserApp();
|
||||
sysUser.setUserName(username);
|
||||
sysUser.setNickName(username);
|
||||
sysUser.setPhonenumber(username);
|
||||
sysUser.setTenantId(tenantId);
|
||||
sysUser.setUserType(registerBody.getUserType());
|
||||
sysUser.setPassword(BCrypt.hashpw(password));
|
||||
sysUser.setUserType(userType);
|
||||
|
||||
appUserService.saveMpUser(sysUser);
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 记录登录信息
|
||||
*
|
||||
* @param tenantId 租户ID
|
||||
* @param username 用户名
|
||||
* @param status 状态
|
||||
* @param message 消息内容
|
||||
* @return
|
||||
*/
|
||||
private void recordLogininfor(String tenantId, String username, String status, String message) {
|
||||
LogininforEvent logininforEvent = new LogininforEvent();
|
||||
logininforEvent.setTenantId(tenantId);
|
||||
logininforEvent.setUsername(username);
|
||||
logininforEvent.setStatus(status);
|
||||
logininforEvent.setMessage(message);
|
||||
logininforEvent.setRequest(ServletUtils.getRequest());
|
||||
// com.fuyuanshen.web.listener.UserActionListener
|
||||
SpringUtils.context().publishEvent(logininforEvent);
|
||||
}
|
||||
|
||||
|
||||
public LoginVo login(LoginUser loginUser, SysClientVo client) {
|
||||
loginUser.setClientKey(client.getClientKey());
|
||||
loginUser.setDeviceType(client.getDeviceType());
|
||||
SaLoginParameter model = new SaLoginParameter();
|
||||
model.setDeviceType(client.getDeviceType());
|
||||
// 自定义分配 不同用户体系 不同 token 授权时间 不设置默认走全局 yml 配置
|
||||
// 例如: 后台用户30分钟过期 app用户1天过期
|
||||
model.setTimeout(client.getTimeout());
|
||||
model.setActiveTimeout(client.getActiveTimeout());
|
||||
model.setExtra(LoginHelper.CLIENT_KEY, client.getClientId());
|
||||
// 生成token
|
||||
LoginHelper.login(loginUser, model);
|
||||
|
||||
LoginVo loginVo = new LoginVo();
|
||||
loginVo.setAccessToken(StpUtil.getTokenValue());
|
||||
loginVo.setExpireIn(StpUtil.getTokenTimeout());
|
||||
loginVo.setClientId(client.getClientId());
|
||||
return loginVo;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建登录用户
|
||||
*/
|
||||
public LoginUser buildLoginUser(UserApp user) {
|
||||
LoginUser loginUser = new LoginUser();
|
||||
Long userId = user.getUserId();
|
||||
loginUser.setTenantId(user.getTenantId());
|
||||
loginUser.setUserId(userId);
|
||||
loginUser.setDeptId(user.getDeptId());
|
||||
loginUser.setUsername(user.getUserName());
|
||||
loginUser.setNickname(user.getNickName());
|
||||
loginUser.setUserType(user.getUserType());
|
||||
// 用户级别
|
||||
// loginUser.setUserLevel(user.getUserLevel());
|
||||
// pid
|
||||
// loginUser.setPid(user.getPid());
|
||||
|
||||
// loginUser.setMenuPermission(permissionService.getMenuPermission(userId));
|
||||
// loginUser.setRolePermission(permissionService.getRolePermission(userId));
|
||||
// if (ObjectUtil.isNotNull(user.getDeptId())) {
|
||||
// Opt<SysDeptVo> deptOpt = Opt.of(user.getDeptId()).map(deptService::selectDeptById);
|
||||
// loginUser.setDeptName(deptOpt.map(SysDeptVo::getDeptName).orElse(StringUtils.EMPTY));
|
||||
// loginUser.setDeptCategory(deptOpt.map(SysDeptVo::getDeptCategory).orElse(StringUtils.EMPTY));
|
||||
// }
|
||||
// List<SysRoleVo> roles = roleService.selectRolesByUserId(userId);
|
||||
// // List<SysPostVo> posts = postService.selectPostsByUserId(userId);
|
||||
// loginUser.setRoles(BeanUtil.copyToList(roles, RoleDTO.class));
|
||||
// loginUser.setPosts(BeanUtil.copyToList(posts, PostDTO.class));
|
||||
return loginUser;
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
package com.fuyuanshen.mp.service;
|
||||
|
||||
import com.fuyuanshen.equipment.domain.UserApp;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
/**
|
||||
* 登录校验方法
|
||||
*
|
||||
* @author Lion Li
|
||||
*/
|
||||
|
||||
|
||||
public interface MPService {
|
||||
|
||||
|
||||
/**
|
||||
* 获取小程序用户信息
|
||||
*
|
||||
* @param phoneNumber 手机号
|
||||
*/
|
||||
UserApp getMpUser(Long phoneNumber);
|
||||
|
||||
UserApp loadUserByUsername(String username);
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
package com.fuyuanshen.mp.service.impl;
|
||||
|
||||
/**
|
||||
* 登录校验方法
|
||||
*
|
||||
* @author Lion Li
|
||||
*/
|
||||
|
||||
public class MPAuthServiceImpl {
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,43 @@
|
||||
package com.fuyuanshen.mp.service.impl;
|
||||
|
||||
import com.fuyuanshen.equipment.domain.UserApp;
|
||||
import com.fuyuanshen.equipment.mapper.UserAppMapper;
|
||||
import com.fuyuanshen.equipment.service.AppUserService;
|
||||
import com.fuyuanshen.mp.service.MPService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
/**
|
||||
* 登录校验方法
|
||||
*
|
||||
* @author Lion Li
|
||||
*/
|
||||
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class MPServiceImpl implements MPService {
|
||||
|
||||
|
||||
private final AppUserService appUserService;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 获取小程序用户信息
|
||||
*
|
||||
* @param phoneNumber 手机号
|
||||
*/
|
||||
@Override
|
||||
public UserApp getMpUser(Long phoneNumber) {
|
||||
return appUserService.getMpUser(phoneNumber);
|
||||
}
|
||||
|
||||
@Override
|
||||
public UserApp loadUserByUsername(String username) {
|
||||
return appUserService.loadUserByUsername(username);
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -111,6 +111,7 @@ public class AuthController {
|
||||
return R.ok(loginVo);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取跳转URL
|
||||
*
|
||||
@ -133,6 +134,7 @@ public class AuthController {
|
||||
return R.ok("操作成功", authorizeUrl);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 前端回调绑定授权(需要token)
|
||||
*
|
||||
|
@ -14,12 +14,10 @@ import com.fuyuanshen.common.core.constant.SystemConstants;
|
||||
import com.fuyuanshen.common.core.domain.model.AppLoginUser;
|
||||
import com.fuyuanshen.common.core.domain.model.SmsLoginBody;
|
||||
import com.fuyuanshen.common.core.enums.LoginType;
|
||||
import com.fuyuanshen.common.core.enums.UserType;
|
||||
import com.fuyuanshen.common.core.exception.user.CaptchaExpireException;
|
||||
import com.fuyuanshen.common.core.exception.user.UserException;
|
||||
import com.fuyuanshen.common.core.utils.MessageUtils;
|
||||
import com.fuyuanshen.common.core.utils.ServletUtils;
|
||||
import com.fuyuanshen.common.core.utils.StringUtils;
|
||||
import com.fuyuanshen.common.core.utils.ValidatorUtils;
|
||||
import com.fuyuanshen.common.core.utils.*;
|
||||
import com.fuyuanshen.common.json.utils.JsonUtils;
|
||||
import com.fuyuanshen.common.redis.utils.RedisUtils;
|
||||
import com.fuyuanshen.common.satoken.utils.AppLoginHelper;
|
||||
@ -60,9 +58,9 @@ public class AppSmsAuthStrategy implements IAuthStrategy {
|
||||
AppUserVo user = loadUserByPhonenumber(phonenumber);
|
||||
if (ObjectUtil.isNull(user)) {
|
||||
//新增Appuser
|
||||
addAppUser(tenantId, phonenumber);
|
||||
|
||||
user = appUserMapper.selectVoOne(new LambdaQueryWrapper<AppUser>().eq(AppUser::getPhonenumber, phonenumber));
|
||||
AppUser appUser = addAppUser(tenantId, phonenumber);
|
||||
MapstructUtils.convert(appUser, user);
|
||||
// user = appUserMapper.selectVoOne(new LambdaQueryWrapper<AppUser>().eq(AppUser::getPhonenumber, phonenumber));
|
||||
// loginService.recordLogininfor(tenantId, phonenumber, Constants.LOGIN_FAIL, MessageUtils.message("user.not.exists", phonenumber));
|
||||
// throw new UserException("user.not.exists", phonenumber);
|
||||
}
|
||||
@ -89,7 +87,7 @@ public class AppSmsAuthStrategy implements IAuthStrategy {
|
||||
return loginVo;
|
||||
}
|
||||
|
||||
private void addAppUser(String tenantId, String phonenumber) {
|
||||
private AppUser addAppUser(String tenantId, String phonenumber) {
|
||||
AppUser appUser = new AppUser();
|
||||
appUser.setPhonenumber(phonenumber);
|
||||
appUser.setUserName(phonenumber);
|
||||
@ -98,6 +96,7 @@ public class AppSmsAuthStrategy implements IAuthStrategy {
|
||||
appUser.setLoginIp(ServletUtils.getClientIP());
|
||||
appUser.setTenantId(tenantId);
|
||||
appUserMapper.insert(appUser);
|
||||
return appUser;
|
||||
}
|
||||
|
||||
|
||||
@ -114,7 +113,9 @@ public class AppSmsAuthStrategy implements IAuthStrategy {
|
||||
}
|
||||
|
||||
private AppUserVo loadUserByPhonenumber(String phonenumber) {
|
||||
AppUserVo user = appUserMapper.selectVoOne(new LambdaQueryWrapper<AppUser>().eq(AppUser::getPhonenumber, phonenumber));
|
||||
AppUserVo user = appUserMapper.selectVoOne(new LambdaQueryWrapper<AppUser>()
|
||||
.eq(AppUser::getPhonenumber, phonenumber)
|
||||
.eq(AppUser::getUserType, UserType.APP_USER.getUserType()));
|
||||
if (ObjectUtil.isNull(user)) {
|
||||
log.info("登录用户:{} 不存在.", phonenumber);
|
||||
// throw new UserException("user.not.exists", phonenumber);
|
||||
@ -126,5 +127,4 @@ public class AppSmsAuthStrategy implements IAuthStrategy {
|
||||
}
|
||||
return user;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -21,7 +21,13 @@ public enum UserType {
|
||||
/**
|
||||
* 移动客户端用户
|
||||
*/
|
||||
APP_USER("app_user");
|
||||
APP_USER("app_user"),
|
||||
|
||||
/**
|
||||
* 小程序 用户
|
||||
*/
|
||||
XCX_USER("xcx_user");
|
||||
|
||||
|
||||
/**
|
||||
* 用户类型标识(用于 token、权限识别等)
|
||||
|
@ -37,7 +37,7 @@ public enum BusinessType {
|
||||
EXPORT,
|
||||
|
||||
/**
|
||||
* 导入
|
||||
*
|
||||
*/
|
||||
IMPORT,
|
||||
|
||||
|
@ -17,8 +17,8 @@ public class TestSMSController {
|
||||
public void testSend() {
|
||||
// 在创建完SmsBlend实例后,再未手动调用注销的情况下框架会持有该实例,可以直接通过指定configId来获取想要的配置,如果你想使用
|
||||
// 负载均衡形式获取实例,只要使用getSmsBlend的无参重载方法即可,如果你仅有一个配置,也可以使用该方法
|
||||
SmsBlend smsBlend = SmsFactory.getSmsBlend("alibaba");
|
||||
SmsResponse smsResponse = smsBlend.sendMessage("18656573389", "123");
|
||||
SmsBlend smsBlend = SmsFactory.getSmsBlend("config1");
|
||||
SmsResponse smsResponse = smsBlend.sendMessage("18656573389", "1234");
|
||||
}
|
||||
|
||||
}
|
@ -2,6 +2,7 @@ package com.fuyuanshen.app.controller;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import com.fuyuanshen.common.core.enums.UserType;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import jakarta.validation.constraints.*;
|
||||
@ -36,12 +37,14 @@ public class AppUserController extends BaseController {
|
||||
|
||||
private final IAppUserService appUserService;
|
||||
|
||||
|
||||
/**
|
||||
* 查询APP用户信息列表
|
||||
*/
|
||||
// @SaCheckPermission("app:user:list")
|
||||
@GetMapping("/list")
|
||||
public TableDataInfo<AppUserVo> list(AppUserBo bo, PageQuery pageQuery) {
|
||||
bo.setUserType(UserType.APP_USER.getUserType());
|
||||
return appUserService.queryPageList(bo, pageQuery);
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,75 @@
|
||||
// package com.fuyuanshen.app.controller.mp;
|
||||
//
|
||||
// import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
// import lombok.RequiredArgsConstructor;
|
||||
// import lombok.extern.slf4j.Slf4j;
|
||||
// import org.springframework.http.ResponseEntity;
|
||||
// import org.springframework.web.bind.annotation.GetMapping;
|
||||
// import org.springframework.web.bind.annotation.RequestBody;
|
||||
// import org.springframework.web.bind.annotation.RequestMapping;
|
||||
// import org.springframework.web.bind.annotation.RestController;
|
||||
//
|
||||
// import java.util.ArrayList;
|
||||
// import java.util.HashMap;
|
||||
// import java.util.List;
|
||||
// import java.util.Map;
|
||||
//
|
||||
// /**
|
||||
// * @author: 默苍璃
|
||||
// * @date: 2025-06-2313:56
|
||||
// */
|
||||
// @Slf4j
|
||||
// @RestController
|
||||
// @RequestMapping("/mp")
|
||||
// @RequiredArgsConstructor
|
||||
// @Tag(name = "小程序:相关接口")
|
||||
// public class MPController {
|
||||
// //
|
||||
// // private final TokenProvider tokenProvider;
|
||||
// // private final SecurityProperties properties;
|
||||
// // private final OnlineUserService onlineUserService;
|
||||
// // private final DeviceService deviceService;
|
||||
// // private final MPService mpService;
|
||||
// //
|
||||
// // @Log("小程序用户登录")
|
||||
// // @ApiOperation("小程序登录授权")
|
||||
// // @AnonymousPostMapping(value = "/login")
|
||||
// // public ResponseEntity<Object> login(@RequestBody AuthUserDto authUser, HttpServletRequest request) throws Exception {
|
||||
// //
|
||||
// // // 获取用户信息
|
||||
// // User user = new User();
|
||||
// // user.setUsername("MP");
|
||||
// // user.setPassword("MP");
|
||||
// // AuthorityDto authorityDto = new AuthorityDto();
|
||||
// // authorityDto.setAuthority("MP");
|
||||
// // List<AuthorityDto> authorityDtos = new ArrayList<>();
|
||||
// // authorityDtos.add(authorityDto);
|
||||
// // user.setPhone(authUser.getPhoneNumber());
|
||||
// // JwtUserDto jwtUser = new JwtUserDto(null, user, null, authorityDtos);
|
||||
// //
|
||||
// // Authentication authentication = new UsernamePasswordAuthenticationToken(jwtUser, null, authorityDtos);
|
||||
// // SecurityContextHolder.getContext().setAuthentication(authentication);
|
||||
// // // 生成令牌
|
||||
// // String token = tokenProvider.createToken(jwtUser);
|
||||
// // // 返回 token 与 用户信息
|
||||
// // Map<String, Object> authInfo = new HashMap<String, Object>(2) {{
|
||||
// // put("token", properties.getTokenStartWith() + token);
|
||||
// // put("user", jwtUser);
|
||||
// // }};
|
||||
// //
|
||||
// // // 保存在线信息
|
||||
// // onlineUserService.save(jwtUser, token, request);
|
||||
// //
|
||||
// // // 返回登录信息
|
||||
// // return ResponseEntity.ok(authInfo);
|
||||
// // }
|
||||
// //
|
||||
// //
|
||||
// // @GetMapping("/queryDevice")
|
||||
// // @ApiOperation("是否存在设备MAC号")
|
||||
// // public ResponseVO<Boolean> queryDevice(@ApiParam("设备mac值") String mac) {
|
||||
// // return ResponseVO.success(mpService.queryDevice(mac));
|
||||
// // }
|
||||
//
|
||||
//
|
||||
// }
|
@ -1,6 +1,9 @@
|
||||
package com.fuyuanshen.app.domain.vo;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.FieldFill;
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import com.fuyuanshen.app.domain.AppUser;
|
||||
import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
|
||||
@ -120,4 +123,10 @@ public class AppUserVo implements Serializable {
|
||||
* 部门ID
|
||||
*/
|
||||
private Long deptId;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
private Date createTime;
|
||||
|
||||
}
|
||||
|
@ -6,15 +6,18 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
|
||||
<select id="queryAppFileList" resultType="com.fuyuanshen.app.domain.vo.AppFileVo">
|
||||
select a.id,a.business_id,a.file_id,b.file_name,b.url fileUrl from app_business_file a left join sys_oss b on a.file_id = b.oss_id
|
||||
where
|
||||
where 1=1
|
||||
<if test="businessId != null">
|
||||
and a.business_id = #{businessId}
|
||||
</if>
|
||||
<if test="fileId != null">
|
||||
and a.file_id = #{fileId}
|
||||
</if>
|
||||
<if test="fileType != null">
|
||||
and a.file_type = #{fileType}
|
||||
</if>
|
||||
<if test="createBy != null">
|
||||
a.create_by = #{createBy}
|
||||
and a.create_by = #{createBy}
|
||||
</if>
|
||||
order by a.create_time desc
|
||||
</select>
|
||||
|
@ -3,6 +3,7 @@ package com.fuyuanshen.customer.controller;
|
||||
import cn.hutool.core.collection.CollectionUtil;
|
||||
import cn.hutool.crypto.digest.BCrypt;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.fuyuanshen.common.core.domain.R;
|
||||
import com.fuyuanshen.common.core.domain.ResponseVO;
|
||||
import com.fuyuanshen.common.core.utils.StringUtils;
|
||||
import com.fuyuanshen.common.mybatis.core.page.TableDataInfo;
|
||||
@ -51,43 +52,43 @@ public class CustomerController {
|
||||
|
||||
@GetMapping(value = "/allCustomer")
|
||||
@Operation(summary = "查询所有客户")
|
||||
public ResponseVO<List<Customer>> queryAllCustomer(UserQueryCriteria criteria) {
|
||||
public R<List<Customer>> queryAllCustomer(UserQueryCriteria criteria) {
|
||||
List<Customer> customers = customerService.queryAllCustomers(criteria);
|
||||
return ResponseVO.success(customers);
|
||||
return R.ok(customers);
|
||||
}
|
||||
|
||||
|
||||
// @Log("新增客户")
|
||||
@Operation(summary = "新增客户")
|
||||
@PostMapping(value = "/addCustomer")
|
||||
public ResponseVO<Object> addCustomer(@Validated @RequestBody Customer customer) throws BadRequestException {
|
||||
public R<Void> addCustomer(@Validated @RequestBody Customer customer) throws BadRequestException {
|
||||
if (StringUtils.isBlank(customer.getPassword())) {
|
||||
throw new BadRequestException("账号密码不能为空");
|
||||
}
|
||||
customer.setPassword(BCrypt.hashpw(customer.getPassword()));
|
||||
customerService.addCustomer(customer);
|
||||
return ResponseVO.success("新增客户成功!!!");
|
||||
return R.ok();
|
||||
}
|
||||
|
||||
|
||||
// @Log("修改客户")
|
||||
@Operation(summary = "修改客户")
|
||||
@PutMapping(value = "updateCustomer")
|
||||
public ResponseVO<Object> updateCustomer(@RequestBody Customer resources) throws Exception {
|
||||
public R<Void> updateCustomer(@RequestBody Customer resources) throws Exception {
|
||||
customerService.updateCustomer(resources);
|
||||
return ResponseVO.success(null);
|
||||
return R.ok();
|
||||
}
|
||||
|
||||
|
||||
// @Log("删除客户")
|
||||
@Operation(summary = "删除客户")
|
||||
@DeleteMapping(value = "/deleteCustomer")
|
||||
public ResponseVO<Object> deleteCustomer(@RequestBody Set<Long> ids) throws BadRequestException {
|
||||
public R<Void> deleteCustomer(@RequestBody Set<Long> ids) throws BadRequestException {
|
||||
if (CollectionUtil.isEmpty(ids)) {
|
||||
throw new BadRequestException("请选择要删除的客户");
|
||||
}
|
||||
customerService.delete(ids);
|
||||
return ResponseVO.success(null);
|
||||
return R.ok();
|
||||
}
|
||||
|
||||
|
||||
|
@ -50,6 +50,12 @@ public class UserQueryCriteria extends BaseEntity {
|
||||
@Schema(name = "是否启用")
|
||||
private Boolean enabled;
|
||||
|
||||
/**
|
||||
* 帐号状态(0正常 1停用)
|
||||
*/
|
||||
@Schema(name = "帐号状态(0正常 1停用)")
|
||||
private String status;
|
||||
|
||||
@Schema(name = "部门ID")
|
||||
private Long deptId;
|
||||
|
||||
|
@ -58,6 +58,12 @@ public class ConsumerVo extends TenantEntity {
|
||||
@Schema(name = "是否启用")
|
||||
private Boolean enabled;
|
||||
|
||||
/**
|
||||
* 帐号状态(0正常 1停用)
|
||||
*/
|
||||
@Schema(name = "帐号状态(0正常 1停用)")
|
||||
private String status;
|
||||
|
||||
@Schema(name = "是否为admin账号", hidden = true)
|
||||
private Boolean isAdmin = false;
|
||||
|
||||
|
@ -112,6 +112,11 @@ public class CustomerServiceImpl extends ServiceImpl<CustomerMapper, Customer> i
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void updateCustomer(Customer resources) throws Exception {
|
||||
if (resources.getEnabled()) {
|
||||
resources.setStatus("0");
|
||||
} else {
|
||||
resources.setStatus("1");
|
||||
}
|
||||
saveOrUpdate(resources);
|
||||
}
|
||||
|
||||
|
@ -68,8 +68,8 @@
|
||||
<if test="criteria.id != null">
|
||||
and u1.user_id = #{criteria.id}
|
||||
</if>
|
||||
<if test="criteria.enabled != null">
|
||||
and u1.enabled = #{criteria.enabled}
|
||||
<if test="criteria.status != null">
|
||||
and u.status = #{criteria.status}
|
||||
</if>
|
||||
<if test="criteria.deptIds != null and criteria.deptIds.size() != 0">
|
||||
and u1.dept_id in
|
||||
@ -94,7 +94,7 @@
|
||||
<!-- 分页查询客户 -->
|
||||
<select id="findCustomers" resultType="com.fuyuanshen.customer.domain.Customer">
|
||||
select
|
||||
u.user_id as customerId, u.nick_name , u.user_name, u.enabled, u.create_time
|
||||
u.user_id as customerId, u.nick_name , u.user_name, u.enabled, u.create_time,u.status
|
||||
from sys_user u
|
||||
<where>
|
||||
<if test="criteria.ids != null and !criteria.ids.isEmpty()">
|
||||
@ -109,8 +109,8 @@
|
||||
<if test="criteria.blurry != null and criteria.blurry.trim() != ''">
|
||||
and u.nick_name like concat('%', TRIM(#{criteria.blurry}), '%')
|
||||
</if>
|
||||
<if test="criteria.enabled != null">
|
||||
and u.enabled = #{criteria.enabled}
|
||||
<if test="criteria.status != null">
|
||||
and u.status = #{criteria.status}
|
||||
</if>
|
||||
<if test="criteria.params.beginTime != null and criteria.params.endTime != null">
|
||||
and u.create_time between #{criteria.params.beginTime} and #{criteria.params.endTime}
|
||||
@ -139,8 +139,8 @@
|
||||
<if test="criteria.blurry != null and criteria.blurry.trim() != ''">
|
||||
and u.nick_name like concat('%', TRIM(#{criteria.blurry}), '%')
|
||||
</if>
|
||||
<if test="criteria.enabled != null">
|
||||
and u.enabled = #{criteria.enabled}
|
||||
<if test="criteria.status != null">
|
||||
and u.status = #{criteria.status}
|
||||
</if>
|
||||
<if test="criteria.params.beginTime != null and criteria.params.endTime != null">
|
||||
and u.create_time between #{criteria.params.beginTime} and #{criteria.params.endTime}
|
||||
|
@ -103,6 +103,11 @@
|
||||
<artifactId>fys-common-websocket</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.bytedeco</groupId>
|
||||
<artifactId>javacv-platform</artifactId>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
|
@ -0,0 +1,244 @@
|
||||
package com.fuyuanshen.demo.controller;
|
||||
|
||||
import cn.dev33.satoken.annotation.SaIgnore;
|
||||
import com.fuyuanshen.common.core.domain.R;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.nio.file.Files;
|
||||
import java.util.Base64;
|
||||
import java.util.List;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.bytedeco.javacv.FFmpegFrameGrabber;
|
||||
import org.bytedeco.javacv.Frame;
|
||||
import org.bytedeco.javacv.Java2DFrameUtils;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/video")
|
||||
public class VideoUploadController {
|
||||
|
||||
// 可配置项:建议从 application.yml 中读取
|
||||
private static final int MAX_VIDEO_SIZE = 10 * 1024 * 1024; // 10 MB
|
||||
private static final int FRAME_RATE = 15; // 每秒抽15帧
|
||||
private static final int DURATION = 2; // 抽2秒
|
||||
private static final int TOTAL_FRAMES = FRAME_RATE * DURATION;
|
||||
private static final int WIDTH = 160;
|
||||
private static final int HEIGHT = 80;
|
||||
private static final char[] HEX_ARRAY = "0123456789ABCDEF".toCharArray();
|
||||
|
||||
@PostMapping(value = "/upload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
|
||||
@SaIgnore
|
||||
public R<List<String>> upload(@RequestParam("file") MultipartFile file) {
|
||||
if (file == null || file.isEmpty()) {
|
||||
return R.fail("上传文件不能为空");
|
||||
}
|
||||
|
||||
if (!isVideo(file.getOriginalFilename())) {
|
||||
return R.fail("只允许上传视频文件");
|
||||
}
|
||||
|
||||
if (file.getSize() > MAX_VIDEO_SIZE) {
|
||||
return R.fail("视频大小不能超过10MB");
|
||||
}
|
||||
|
||||
File tempFile = null;
|
||||
try {
|
||||
// 创建临时文件保存上传的视频
|
||||
tempFile = createTempVideoFile(file);
|
||||
|
||||
|
||||
List<BufferedImage> frames = extractFramesFromVideo(tempFile);
|
||||
if (frames.isEmpty()) {
|
||||
return R.fail("无法提取任何帧");
|
||||
}
|
||||
|
||||
// ✅ 新增:保存帧为图片
|
||||
//saveFramesToLocal(frames, "extracted_frame");
|
||||
|
||||
byte[] binaryData = convertFramesToRGB565(frames);
|
||||
// String base64Data = Base64.getEncoder().encodeToString(binaryData);
|
||||
//
|
||||
// return R.ok(base64Data);
|
||||
// 构造响应头
|
||||
// 将二进制数据转为 Hex 字符串
|
||||
// 转换为 Hex 字符串列表
|
||||
List<String> hexList = bytesToHexList(binaryData);
|
||||
|
||||
return R.ok(hexList);
|
||||
|
||||
} catch (Exception e) {
|
||||
return R.fail("视频处理失败:" + e.getMessage());
|
||||
} finally {
|
||||
deleteTempFile(tempFile);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* rgb565 转 hex
|
||||
*/
|
||||
private List<String> bytesToHexList(byte[] bytes) {
|
||||
List<String> hexList = new ArrayList<>();
|
||||
for (byte b : bytes) {
|
||||
int value = b & 0xFF;
|
||||
char high = HEX_ARRAY[value >>> 4];
|
||||
char low = HEX_ARRAY[value & 0x0F];
|
||||
hexList.add(String.valueOf(high) + low);
|
||||
}
|
||||
return hexList;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建临时文件并保存上传的视频
|
||||
*/
|
||||
private File createTempVideoFile(MultipartFile file) throws Exception {
|
||||
File tempFile = Files.createTempFile("upload-", ".mp4").toFile();
|
||||
file.transferTo(tempFile);
|
||||
return tempFile;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从视频中按时间均匀提取指定数量的帧
|
||||
*/
|
||||
private List<BufferedImage> extractFramesFromVideo(File videoFile) throws Exception {
|
||||
List<BufferedImage> frames = new ArrayList<>();
|
||||
|
||||
try (FFmpegFrameGrabber grabber = FFmpegFrameGrabber.createDefault(videoFile)) {
|
||||
grabber.start();
|
||||
|
||||
// 获取视频总帧数和帧率
|
||||
long totalFramesInVideo = grabber.getLengthInFrames();
|
||||
int fps = (int) Math.round(grabber.getFrameRate());
|
||||
if (fps <= 0) fps = 30;
|
||||
|
||||
double durationSeconds = (double) totalFramesInVideo / fps;
|
||||
|
||||
if (durationSeconds < DURATION) {
|
||||
throw new IllegalArgumentException("视频太短,至少需要 " + DURATION + " 秒");
|
||||
}
|
||||
|
||||
// 计算每帧之间的间隔(浮点以实现更精确跳转)
|
||||
double frameInterval = (double) totalFramesInVideo / TOTAL_FRAMES;
|
||||
|
||||
for (int i = 0; i < TOTAL_FRAMES; i++) {
|
||||
int targetFrameNumber = (int) Math.round(i * frameInterval);
|
||||
|
||||
// 避免设置无效帧号
|
||||
if (targetFrameNumber >= totalFramesInVideo) {
|
||||
throw new IllegalArgumentException("目标帧超出范围: " + targetFrameNumber + " ");
|
||||
}
|
||||
|
||||
grabber.setFrameNumber(targetFrameNumber);
|
||||
Frame frame = grabber.grab();
|
||||
|
||||
if (frame != null && frame.image != null) {
|
||||
BufferedImage bufferedImage = Java2DFrameUtils.toBufferedImage(frame);
|
||||
frames.add(cropImage(bufferedImage, WIDTH, HEIGHT));
|
||||
} else {
|
||||
throw new IllegalArgumentException("无法获取第 " + targetFrameNumber + "帧 ");
|
||||
}
|
||||
}
|
||||
|
||||
grabber.stop();
|
||||
}
|
||||
|
||||
return frames;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将抽取的帧保存到本地,用于调试
|
||||
*/
|
||||
private void saveFramesToLocal(List<BufferedImage> frames, String prefix) {
|
||||
// 指定输出目录
|
||||
File outputDir = new File("output_frames");
|
||||
if (!outputDir.exists()) {
|
||||
outputDir.mkdirs();
|
||||
}
|
||||
|
||||
int index = 0;
|
||||
for (BufferedImage frame : frames) {
|
||||
try {
|
||||
File outputImage = new File(outputDir, prefix + "_" + (index++) + ".png");
|
||||
ImageIO.write(frame, "png", outputImage);
|
||||
System.out.println("保存帧图片成功: " + outputImage.getAbsolutePath());
|
||||
} catch (Exception e) {
|
||||
throw new IllegalArgumentException("保存帧图片失败 " + e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 将所有帧转换为 RGB565 格式字节数组
|
||||
*/
|
||||
private byte[] convertFramesToRGB565(List<BufferedImage> frames) throws Exception {
|
||||
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
|
||||
|
||||
for (BufferedImage image : frames) {
|
||||
byte[] rgb565Bytes = convertToRGB565(image);
|
||||
byteArrayOutputStream.write(rgb565Bytes);
|
||||
}
|
||||
|
||||
return byteArrayOutputStream.toByteArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否是支持的视频格式
|
||||
*/
|
||||
private boolean isVideo(String filename) {
|
||||
String ext = filename.substring(filename.lastIndexOf('.')).toLowerCase();
|
||||
return Arrays.asList(".mp4", ".avi", ".mov", ".mkv").contains(ext);
|
||||
}
|
||||
|
||||
/**
|
||||
* 裁剪图像到目标尺寸
|
||||
*/
|
||||
private BufferedImage cropImage(BufferedImage img, int targetWidth, int targetHeight) {
|
||||
int w = Math.min(img.getWidth(), targetWidth);
|
||||
int h = Math.min(img.getHeight(), targetHeight);
|
||||
return img.getSubimage(0, 0, w, h);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将 BufferedImage 转换为 RGB565 格式的字节数组
|
||||
*/
|
||||
private byte[] convertToRGB565(BufferedImage image) {
|
||||
int width = image.getWidth();
|
||||
int height = image.getHeight();
|
||||
byte[] result = new byte[width * height * 2]; // RGB565: 2 bytes per pixel
|
||||
int index = 0;
|
||||
|
||||
for (int y = 0; y < height; y++) {
|
||||
for (int x = 0; x < width; x++) {
|
||||
int rgb = image.getRGB(x, y);
|
||||
int r = ((rgb >> 16) & 0xFF) >> 3;
|
||||
int g = ((rgb >> 8) & 0xFF) >> 2;
|
||||
int b = (rgb & 0xFF) >> 3;
|
||||
short pixel = (short) ((r << 11) | (g << 5) | b);
|
||||
|
||||
result[index++] = (byte) (pixel >> 8); // High byte first
|
||||
result[index++] = (byte) pixel;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除临时文件
|
||||
*/
|
||||
private void deleteTempFile(File file) {
|
||||
if (file != null && file.exists()) {
|
||||
if (!file.delete()) {
|
||||
throw new IllegalArgumentException("无法删除临时文件: " + file.getAbsolutePath());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -6,17 +6,17 @@ import com.fuyuanshen.common.core.validate.EditGroup;
|
||||
import com.fuyuanshen.common.idempotent.annotation.RepeatSubmit;
|
||||
import com.fuyuanshen.common.log.annotation.Log;
|
||||
import com.fuyuanshen.common.log.enums.BusinessType;
|
||||
import com.fuyuanshen.common.mybatis.core.page.TableDataInfo;
|
||||
import com.fuyuanshen.common.web.core.BaseController;
|
||||
import com.fuyuanshen.equipment.domain.bo.UserAppBo;
|
||||
import com.fuyuanshen.equipment.domain.query.DeviceQueryCriteria;
|
||||
import com.fuyuanshen.equipment.service.AppUserService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.bind.annotation.PutMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
/**
|
||||
* @Description:
|
||||
|
@ -4,11 +4,13 @@ package com.fuyuanshen.equipment.controller;
|
||||
import com.alibaba.excel.EasyExcel;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.fuyuanshen.common.core.constant.ResponseMessageConstants;
|
||||
import com.fuyuanshen.common.core.domain.R;
|
||||
import com.fuyuanshen.common.core.domain.ResponseVO;
|
||||
import com.fuyuanshen.common.core.domain.model.LoginUser;
|
||||
import com.fuyuanshen.common.core.utils.file.FileUtil;
|
||||
import com.fuyuanshen.common.mybatis.core.page.TableDataInfo;
|
||||
import com.fuyuanshen.common.satoken.utils.LoginHelper;
|
||||
import com.fuyuanshen.common.web.core.BaseController;
|
||||
import com.fuyuanshen.customer.mapper.CustomerMapper;
|
||||
import com.fuyuanshen.equipment.domain.Device;
|
||||
import com.fuyuanshen.equipment.domain.dto.DeviceExcelImportDTO;
|
||||
@ -49,7 +51,7 @@ import java.util.List;
|
||||
@RestController
|
||||
@RequiredArgsConstructor
|
||||
@RequestMapping("/api/device")
|
||||
public class DeviceController {
|
||||
public class DeviceController extends BaseController {
|
||||
|
||||
private final ISysOssService ossService;
|
||||
private final DeviceService deviceService;
|
||||
@ -72,106 +74,106 @@ public class DeviceController {
|
||||
// @Log("新增设备")
|
||||
@Operation(summary = "新增设备")
|
||||
@PostMapping(value = "/add")
|
||||
public ResponseVO<Object> addDevice(@Validated @ModelAttribute DeviceForm deviceForm) {
|
||||
public R<Void> addDevice(@Validated @ModelAttribute DeviceForm deviceForm) {
|
||||
try {
|
||||
deviceService.addDevice(deviceForm);
|
||||
} catch (Exception e) {
|
||||
log.error("addDevice error: " + e.getMessage());
|
||||
return ResponseVO.fail(e.getMessage());
|
||||
return R.fail(e.getMessage());
|
||||
}
|
||||
return ResponseVO.success(null);
|
||||
return R.ok();
|
||||
}
|
||||
|
||||
|
||||
// @Log("修改设备")
|
||||
@Operation(summary = "修改设备")
|
||||
@PutMapping(value = "/update")
|
||||
public ResponseVO<Object> updateDevice(@Validated @ModelAttribute DeviceForm deviceForm) {
|
||||
public R<Void> updateDevice(@Validated @ModelAttribute DeviceForm deviceForm) {
|
||||
try {
|
||||
deviceService.update(deviceForm);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
log.error("updateDevice error: " + e.getMessage());
|
||||
return ResponseVO.fail("出错了");
|
||||
return R.fail(e.getMessage());
|
||||
}
|
||||
return ResponseVO.success(null);
|
||||
return R.ok();
|
||||
}
|
||||
|
||||
|
||||
@Operation(summary = "设备详情")
|
||||
@GetMapping(value = "/detail/{id}")
|
||||
public ResponseVO<Object> getDevice(@PathVariable Long id) {
|
||||
public R<Device> getDevice(@PathVariable Long id) {
|
||||
Device device = deviceService.getById(id);
|
||||
return ResponseVO.success(device);
|
||||
return R.ok(device);
|
||||
}
|
||||
|
||||
|
||||
// @Log("删除设备")
|
||||
@Operation(summary = "删除设备")
|
||||
@DeleteMapping(value = "/delete")
|
||||
public ResponseVO<Object> deleteDevice(@Parameter(name = "传ID数组[]") @RequestBody List<Long> ids) {
|
||||
public R<Void> deleteDevice(@Parameter(name = "传ID数组[]") @RequestBody List<Long> ids) {
|
||||
deviceService.deleteAll(ids);
|
||||
return ResponseVO.success(ResponseMessageConstants.DELETE_SUCCESS);
|
||||
return R.ok();
|
||||
}
|
||||
|
||||
|
||||
@Operation(summary = "设备定位")
|
||||
@GetMapping(value = "/locateDevice")
|
||||
public ResponseVO<Object> locateDevice(DeviceQueryCriteria criteria) throws IOException {
|
||||
public R<List<Device>> locateDevice(DeviceQueryCriteria criteria) throws IOException {
|
||||
List<Device> devices = deviceService.queryAllDevices(criteria);
|
||||
return ResponseVO.success(devices);
|
||||
return R.ok(devices);
|
||||
}
|
||||
|
||||
|
||||
// @Log("分配客户")
|
||||
@Operation(summary = "分配客户")
|
||||
@PutMapping(value = "/assignCustomer")
|
||||
public ResponseVO<Object> assignCustomer(@Validated @RequestBody CustomerVo customerVo) {
|
||||
public R<Void> assignCustomer(@Validated @RequestBody CustomerVo customerVo) {
|
||||
deviceService.assignCustomer(customerVo);
|
||||
return ResponseVO.success(null);
|
||||
return R.ok();
|
||||
}
|
||||
|
||||
|
||||
// @Log("撤回设备")
|
||||
@Operation(summary = "撤回分配设备")
|
||||
@PostMapping(value = "/withdraw")
|
||||
public ResponseVO<Object> withdrawDevice(@RequestBody List<Long> ids) {
|
||||
public R<Void> withdrawDevice(@RequestBody List<Long> ids) {
|
||||
try {
|
||||
deviceService.withdrawDevice(ids);
|
||||
} catch (Exception e) {
|
||||
log.error("updateDevice error: " + e.getMessage());
|
||||
return ResponseVO.fail("出错了");
|
||||
return R.fail(e.getMessage());
|
||||
}
|
||||
return ResponseVO.success(null);
|
||||
return R.ok();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param deviceForm
|
||||
* @param id
|
||||
* @return
|
||||
* @ModelAttribute 主要用于将请求参数绑定到 Java 对象上,它会从 HTTP 请求的查询参数(Query Parameters)
|
||||
* 或表单数据(Form Data)中提取值,并自动填充到指定的对象属性中。
|
||||
*/
|
||||
// @Log("解绑设备")
|
||||
@Operation(summary = "解绑设备")
|
||||
@PostMapping(value = "/unbind")
|
||||
public ResponseVO<Object> unbindDevice(@Validated @ModelAttribute DeviceForm deviceForm) {
|
||||
deviceService.unbindDevice(deviceForm);
|
||||
return ResponseVO.success("解绑成功!!!");
|
||||
@Operation(summary = "WEB端解绑设备")
|
||||
@GetMapping(value = "/unbind")
|
||||
public R<Void> unbindDevice(@Validated Long id) {
|
||||
return toAjax(deviceService.webUnBindDevice(id));
|
||||
}
|
||||
|
||||
|
||||
@Operation(summary = "导出数据设备")
|
||||
@GetMapping(value = "/download")
|
||||
public void exportDevice(HttpServletResponse response, DeviceQueryCriteria criteria) throws IOException {
|
||||
public R<Void> exportDevice(HttpServletResponse response, DeviceQueryCriteria criteria) throws IOException {
|
||||
List<Device> devices = deviceService.queryAll(criteria);
|
||||
exportService.export(devices, response);
|
||||
return R.ok();
|
||||
}
|
||||
|
||||
|
||||
@Operation(summary = "导入设备数据")
|
||||
@PostMapping(value = "/import", consumes = "multipart/form-data")
|
||||
public ResponseVO<ImportResult> importData(@Parameter(name = "文件", required = true) @RequestPart("file") MultipartFile file) throws BadRequestException {
|
||||
public R<ImportResult> importData(@Parameter(name = "文件", required = true) @RequestPart("file") MultipartFile file) throws BadRequestException {
|
||||
|
||||
String suffix = FileUtil.getExtensionName(file.getOriginalFilename());
|
||||
if (!("xlsx".equalsIgnoreCase(suffix))) {
|
||||
@ -195,13 +197,13 @@ public class DeviceController {
|
||||
// 设置响应消息
|
||||
String message = String.format("成功导入 %d 条数据,失败 %d 条", result.getSuccessCount(), result.getFailureCount());
|
||||
// 返回带有正确泛型的响应
|
||||
return ResponseVO.<ImportResult>success(message, result);
|
||||
return R.ok(message, result);
|
||||
} catch (Exception e) {
|
||||
log.error("导入设备数据出错: {}", e.getMessage(), e);
|
||||
// 在异常情况下,设置默认结果
|
||||
String errorMessage = String.format("导入失败: %s。成功 %d 条,失败 %d 条", e.getMessage(), result.getSuccessCount(), result.getFailureCount());
|
||||
// 使用新方法确保类型正确
|
||||
return ResponseVO.<ImportResult>fail(errorMessage, result);
|
||||
return R.fail(errorMessage, result);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
package com.fuyuanshen.equipment.controller;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.fuyuanshen.common.core.domain.R;
|
||||
import com.fuyuanshen.common.core.domain.ResponseVO;
|
||||
import com.fuyuanshen.common.mybatis.core.page.TableDataInfo;
|
||||
import com.fuyuanshen.equipment.domain.DeviceType;
|
||||
@ -40,43 +41,45 @@ public class DeviceTypeController {
|
||||
|
||||
@GetMapping(value = "/all")
|
||||
@Operation(summary = "查询所有设备类型")
|
||||
public ResponseVO<Object> queryDeviceTypes() {
|
||||
public R<List<DeviceType>> queryDeviceTypes() {
|
||||
List<DeviceType> deviceTypes = deviceTypeService.queryDeviceTypes();
|
||||
return ResponseVO.success(deviceTypes);
|
||||
return R.ok(deviceTypes);
|
||||
}
|
||||
|
||||
|
||||
// @Log("新增设备类型")
|
||||
@Operation(summary = "新增设备类型")
|
||||
@PostMapping(value = "/add")
|
||||
public ResponseVO<Object> createDeviceType(@Validated @RequestBody DeviceType resources) {
|
||||
public R<Void> createDeviceType(@Validated @RequestBody DeviceType resources) {
|
||||
deviceTypeService.create(resources);
|
||||
return ResponseVO.success(null);
|
||||
return R.ok();
|
||||
}
|
||||
|
||||
|
||||
// @Log("修改设备类型")
|
||||
@Operation(summary = "修改设备类型")
|
||||
@PutMapping(value = "/update")
|
||||
public ResponseVO<Object> updateDeviceType(@Validated @RequestBody DeviceTypeForm resources) {
|
||||
public R<Void> updateDeviceType(@Validated @RequestBody DeviceTypeForm resources) {
|
||||
deviceTypeService.update(resources);
|
||||
return ResponseVO.success(null);
|
||||
return R.ok();
|
||||
}
|
||||
|
||||
|
||||
// @Log("删除设备类型")
|
||||
@Operation(summary = "删除设备类型")
|
||||
@DeleteMapping(value = "/delete")
|
||||
public ResponseVO<Object> deleteDeviceType(@Parameter(name = "传ID数组[]") @RequestBody List<Long> ids) {
|
||||
public R<Void> deleteDeviceType(@Parameter(name = "传ID数组[]") @RequestBody List<Long> ids) {
|
||||
deviceTypeService.deleteAll(ids);
|
||||
return ResponseVO.success("删除成功!!!");
|
||||
return R.ok();
|
||||
}
|
||||
|
||||
|
||||
@GetMapping(value = "/communicationMode")
|
||||
@Operation(summary = "获取设备类型通讯方式")
|
||||
public ResponseVO<DeviceType> getCommunicationMode(@Parameter(name = "设备类型ID", required = true) Long id) {
|
||||
return ResponseVO.success(deviceTypeService.getCommunicationMode(id));
|
||||
public R<DeviceType> getCommunicationMode(@Parameter(name = "设备类型ID", required = true) Long id) {
|
||||
DeviceType communicationMode = deviceTypeService.getCommunicationMode(id);
|
||||
return R.ok(communicationMode);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -9,6 +9,8 @@ import com.fuyuanshen.common.tenant.core.TenantEntity;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* @Description: 设备表
|
||||
* @Author: WY
|
||||
@ -114,4 +116,6 @@ public class Device extends TenantEntity {
|
||||
private String createByName;
|
||||
|
||||
private Long bindingUserId;
|
||||
|
||||
private Date bindingTime;
|
||||
}
|
||||
|
@ -34,7 +34,7 @@ public class DeviceAssignments extends TenantEntity {
|
||||
private Long fromCustomerId;
|
||||
|
||||
/**
|
||||
* 接收方
|
||||
* 接收方(当前设备所处位置)
|
||||
*/
|
||||
private Long toCustomerId;
|
||||
|
||||
|
@ -30,6 +30,11 @@ public class UserApp extends TenantEntity {
|
||||
@TableId(value = "user_id")
|
||||
private Long userId;
|
||||
|
||||
/**
|
||||
* 部门ID
|
||||
*/
|
||||
private Long deptId;
|
||||
|
||||
/**
|
||||
* 用户账号
|
||||
*/
|
||||
@ -42,6 +47,7 @@ public class UserApp extends TenantEntity {
|
||||
|
||||
/**
|
||||
* 用户类型(app_user系统用户)
|
||||
* 用户类型(app_user手机端,xcx_user小程序)
|
||||
*/
|
||||
private String userType;
|
||||
|
||||
|
@ -0,0 +1,38 @@
|
||||
package com.fuyuanshen.equipment.enums;
|
||||
|
||||
/**
|
||||
* 用户类型枚举
|
||||
*
|
||||
* @author: 默苍璃
|
||||
* @date: 2025-07-1210:31
|
||||
*/
|
||||
public enum AppUserTypeEnum {
|
||||
|
||||
APP_USER("app_user", "系统用户"),
|
||||
XCX_USER("xcx_user", "小程序用户");
|
||||
|
||||
private final String code;
|
||||
private final String description;
|
||||
|
||||
AppUserTypeEnum(String code, String description) {
|
||||
this.code = code;
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
public String getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "UserType{" +
|
||||
"code='" + code + '\'' +
|
||||
", description='" + description + '\'' +
|
||||
'}';
|
||||
}
|
||||
|
||||
}
|
@ -2,6 +2,7 @@ package com.fuyuanshen.equipment.service;
|
||||
|
||||
import com.fuyuanshen.common.mybatis.core.page.PageQuery;
|
||||
import com.fuyuanshen.common.mybatis.core.page.TableDataInfo;
|
||||
import com.fuyuanshen.equipment.domain.UserApp;
|
||||
import com.fuyuanshen.equipment.domain.bo.UserAppBo;
|
||||
|
||||
import java.util.Collection;
|
||||
@ -23,5 +24,23 @@ public interface AppUserService {
|
||||
*/
|
||||
Boolean updateByBo(UserAppBo bo);
|
||||
|
||||
/**
|
||||
* 查询小程序用户信息
|
||||
*
|
||||
* @param phoneNumber
|
||||
* @return
|
||||
*/
|
||||
UserApp getMpUser(Long phoneNumber);
|
||||
|
||||
UserApp loadUserByUsername(String username);
|
||||
|
||||
/**
|
||||
* 新增小程序用户信息
|
||||
*
|
||||
* @param userApp
|
||||
* @return
|
||||
*/
|
||||
void saveMpUser(UserApp userApp);
|
||||
|
||||
|
||||
}
|
||||
|
@ -91,4 +91,19 @@ public interface DeviceService extends IService<Device> {
|
||||
|
||||
int unBindDevice(Long id);
|
||||
|
||||
/**
|
||||
* 检查设备MAC值是否存在
|
||||
*
|
||||
* @param mac
|
||||
* @return
|
||||
*/
|
||||
Boolean queryDevice(String mac);
|
||||
|
||||
/**
|
||||
* WEB端解绑设备
|
||||
*
|
||||
* @param id
|
||||
* @return
|
||||
*/
|
||||
int webUnBindDevice(Long id);
|
||||
}
|
||||
|
@ -10,6 +10,8 @@ import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.fuyuanshen.common.core.domain.model.LoginUser;
|
||||
import com.fuyuanshen.common.core.exception.BadRequestException;
|
||||
import com.fuyuanshen.common.core.utils.StringUtils;
|
||||
import com.fuyuanshen.common.mybatis.core.page.PageQuery;
|
||||
import com.fuyuanshen.common.mybatis.core.page.TableDataInfo;
|
||||
import com.fuyuanshen.common.satoken.utils.AppLoginHelper;
|
||||
@ -42,6 +44,7 @@ import com.fuyuanshen.system.domain.vo.SysOssVo;
|
||||
import com.fuyuanshen.system.service.ISysOssService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.poi.ss.formula.functions.T;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
@ -54,6 +57,7 @@ import java.time.LocalDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* @Description:
|
||||
@ -272,20 +276,23 @@ public class DeviceServiceImpl extends ServiceImpl<DeviceMapper, Device> impleme
|
||||
public void deleteAll(List<Long> ids) {
|
||||
List<Long> invalidIds = new ArrayList<>();
|
||||
|
||||
for (Long id : ids) {
|
||||
DeviceAssignments deviceAssignment = deviceAssignmentsMapper.selectById(id);
|
||||
Device deviceType = deviceMapper.selectById(deviceAssignment.getDeviceId());
|
||||
|
||||
if (StringUtils.isNotEmpty(deviceAssignment.getAssigneeName())) {
|
||||
throw new BadRequestException(deviceType.getDeviceName() + ":设备已分配,请先解绑设备!!!");
|
||||
}
|
||||
|
||||
// 接收者
|
||||
if (Objects.equals(deviceAssignment.getAssigneeId(), deviceType.getOriginalOwnerId())) {
|
||||
invalidIds.add(deviceAssignment.getDeviceId());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
deviceAssignmentsMapper.deleteByIds(ids);
|
||||
//
|
||||
// for (Long id : ids) {
|
||||
//
|
||||
// Device deviceType = deviceMapper.selectById(id);
|
||||
// if (deviceType == null || !Objects.equals(deviceType.getCurrentOwnerId(), LoginHelper.getUserId())) {
|
||||
// invalidIds.add(id);
|
||||
// }
|
||||
// }
|
||||
// if (!invalidIds.isEmpty()) {
|
||||
// throw new RuntimeException("以下设备无法删除(ID 不存在或无权限): " + invalidIds);
|
||||
// }
|
||||
//
|
||||
// deviceMapper.deleteByIds(ids);
|
||||
deviceMapper.deleteByIds(invalidIds);
|
||||
}
|
||||
|
||||
|
||||
@ -313,9 +320,9 @@ public class DeviceServiceImpl extends ServiceImpl<DeviceMapper, Device> impleme
|
||||
List<DeviceTypeGrants> deviceTypeGrants = new ArrayList<>();
|
||||
for (DeviceAssignments assignment : assignments) {
|
||||
|
||||
if (assignment.getToCustomerId() != null) {
|
||||
if (assignment.getToCustomerId() != null && assignment.getToCustomerId() != 0L) {
|
||||
log.info("设备已经分配客户!!!");
|
||||
continue;
|
||||
throw new RuntimeException("设备已经分配客户!!!");
|
||||
}
|
||||
|
||||
Device device = deviceMapper.selectById(assignment.getDeviceId());
|
||||
@ -406,54 +413,6 @@ public class DeviceServiceImpl extends ServiceImpl<DeviceMapper, Device> impleme
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 撤回设备
|
||||
*
|
||||
* @param ids
|
||||
*/
|
||||
// @Override
|
||||
// public void withdrawDevice(List<Long> ids) {
|
||||
// ids.forEach((id) -> {
|
||||
// List<Device> deviceChain = getDeviceChain(id);
|
||||
// deviceChain.forEach((device) -> {
|
||||
// device.setDeviceStatus(DeviceStatusEnum.INVALID.getCode());
|
||||
// deviceMapper.updateById(device);
|
||||
// });
|
||||
// });
|
||||
//
|
||||
// ids.forEach((id) -> {
|
||||
// Device device = new Device();
|
||||
// device.setId(id);
|
||||
// device.setCustomerId(null);
|
||||
// device.setCustomerName("");
|
||||
// deviceMapper.updateById(device);
|
||||
// });
|
||||
//
|
||||
// }
|
||||
//
|
||||
//
|
||||
// public List<Device> getDeviceChain(Long originalDeviceId) {
|
||||
// List<Device> chain = new ArrayList<>();
|
||||
// Set<Long> visited = new HashSet<>(); // 防止循环引用
|
||||
// findNext(chain, visited, originalDeviceId);
|
||||
// return chain;
|
||||
// }
|
||||
//
|
||||
// private void findNext(List<Device> chain, Set<Long> visited, Long currentOriginalDeviceId) {
|
||||
// if (visited.contains(currentOriginalDeviceId)) {
|
||||
// log.info("检测到循环引用,终止递归");
|
||||
// return;
|
||||
// }
|
||||
// visited.add(currentOriginalDeviceId);
|
||||
//
|
||||
// List<Device> devices = deviceMapper.findByOriginalDeviceId(currentOriginalDeviceId);
|
||||
// for (Device device : devices) {
|
||||
// chain.add(device);
|
||||
// findNext(chain, visited, device.getId());
|
||||
// }
|
||||
// }
|
||||
|
||||
|
||||
/**
|
||||
* 撤回设备
|
||||
*
|
||||
@ -468,7 +427,7 @@ public class DeviceServiceImpl extends ServiceImpl<DeviceMapper, Device> impleme
|
||||
Device device = deviceMapper.selectById(assignment.getDeviceId());
|
||||
// 接收者
|
||||
assignment.setAssigneeName("");
|
||||
assignment.setToCustomerId(null);
|
||||
assignment.setToCustomerId(0L);
|
||||
deviceAssignmentsMapper.updateById(assignment);
|
||||
|
||||
// 获取所有已分配的设备
|
||||
@ -527,8 +486,9 @@ public class DeviceServiceImpl extends ServiceImpl<DeviceMapper, Device> impleme
|
||||
UpdateWrapper<Device> deviceUpdateWrapper = new UpdateWrapper<>();
|
||||
deviceUpdateWrapper.eq("id", device.getId())
|
||||
.set("binding_status", BindingStatusEnum.BOUND.getCode())
|
||||
.set("binding_user_id", userId);
|
||||
;
|
||||
.set("binding_user_id", userId)
|
||||
.set("binding_time", new Date());
|
||||
|
||||
|
||||
return baseMapper.update(null, deviceUpdateWrapper);
|
||||
} else if (mode == CommunicationModeEnum.BLUETOOTH.getValue()) {
|
||||
@ -546,8 +506,9 @@ public class DeviceServiceImpl extends ServiceImpl<DeviceMapper, Device> impleme
|
||||
UpdateWrapper<Device> deviceUpdateWrapper = new UpdateWrapper<>();
|
||||
deviceUpdateWrapper.eq("id", device.getId())
|
||||
.set("binding_status", BindingStatusEnum.BOUND.getCode())
|
||||
.set("binding_user_id", userId);
|
||||
return baseMapper.update(null, qw);
|
||||
.set("binding_user_id", userId)
|
||||
.set("binding_time", new Date());
|
||||
return baseMapper.update(null, deviceUpdateWrapper);
|
||||
} else {
|
||||
throw new RuntimeException("通讯方式错误");
|
||||
}
|
||||
@ -564,8 +525,45 @@ public class DeviceServiceImpl extends ServiceImpl<DeviceMapper, Device> impleme
|
||||
// String mode = deviceType.getCommunicationMode();
|
||||
UpdateWrapper<Device> deviceUpdateWrapper = new UpdateWrapper<>();
|
||||
deviceUpdateWrapper.eq("id", device.getId())
|
||||
.set("binding_status", BindingStatusEnum.UNBOUND.getCode());
|
||||
.set("binding_status", BindingStatusEnum.UNBOUND.getCode())
|
||||
.set("binding_user_id", null)
|
||||
.set("binding_time", null);
|
||||
return baseMapper.update(null, deviceUpdateWrapper);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* WEB端解绑设备
|
||||
*
|
||||
* @param id
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public int webUnBindDevice(Long id) {
|
||||
DeviceAssignments deviceAssignment = deviceAssignmentsMapper.selectById(id);
|
||||
if (deviceAssignment == null) {
|
||||
throw new RuntimeException("请先将设备入库!!!");
|
||||
}
|
||||
return unBindDevice(deviceAssignment.getDeviceId());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 查询设备MAC号
|
||||
*
|
||||
* @param mac
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public Boolean queryDevice(String mac) {
|
||||
QueryWrapper<Device> wrapper = new QueryWrapper<>();
|
||||
wrapper.eq("device_mac", mac);
|
||||
List<Device> deviceList = deviceMapper.selectList(wrapper);
|
||||
if (CollectionUtil.isNotEmpty(deviceList)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -1,8 +1,10 @@
|
||||
package com.fuyuanshen.equipment.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
import com.fuyuanshen.common.core.utils.MapstructUtils;
|
||||
import com.fuyuanshen.equipment.domain.UserApp;
|
||||
import com.fuyuanshen.equipment.domain.bo.UserAppBo;
|
||||
import com.fuyuanshen.equipment.enums.AppUserTypeEnum;
|
||||
import com.fuyuanshen.equipment.mapper.UserAppMapper;
|
||||
import com.fuyuanshen.equipment.service.AppUserService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
@ -22,7 +24,7 @@ import java.util.Collection;
|
||||
@Service
|
||||
public class UserAppServiceImpl implements AppUserService {
|
||||
|
||||
private final UserAppMapper baseMapper;
|
||||
private final UserAppMapper userAppMapper;
|
||||
|
||||
|
||||
/**
|
||||
@ -34,7 +36,42 @@ public class UserAppServiceImpl implements AppUserService {
|
||||
@Override
|
||||
public Boolean updateByBo(UserAppBo bo) {
|
||||
UserApp update = MapstructUtils.convert(bo, UserApp.class);
|
||||
return baseMapper.updateById(update) > 0;
|
||||
return userAppMapper.updateById(update) > 0;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 查询小程序用户信息
|
||||
*
|
||||
* @param phoneNumber
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public UserApp getMpUser(Long phoneNumber) {
|
||||
QueryWrapper<UserApp> queryWrapper = new QueryWrapper<>();
|
||||
queryWrapper.eq("phonenumber", phoneNumber);
|
||||
queryWrapper.eq("user_type", AppUserTypeEnum.XCX_USER.getCode());
|
||||
return userAppMapper.selectOne(queryWrapper);
|
||||
}
|
||||
|
||||
@Override
|
||||
public UserApp loadUserByUsername(String username) {
|
||||
QueryWrapper<UserApp> queryWrapper = new QueryWrapper<>();
|
||||
queryWrapper.eq("user_name", username);
|
||||
queryWrapper.eq("user_type", AppUserTypeEnum.XCX_USER.getCode());
|
||||
return userAppMapper.selectOne(queryWrapper);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 新增小程序用户信息
|
||||
*
|
||||
* @param userApp
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public void saveMpUser(UserApp userApp) {
|
||||
userAppMapper.insert(userApp);
|
||||
}
|
||||
|
||||
|
||||
|
@ -0,0 +1,239 @@
|
||||
package com.fuyuanshen.equipment.utils.c;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
import java.awt.*;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.File;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 可靠点阵生成工具 - 终极版
|
||||
*
|
||||
* @author: 默苍璃
|
||||
* @date: 2025-07-15
|
||||
*/
|
||||
public class ReliableTextToBitmap {
|
||||
|
||||
public static void main(String[] args) throws IOException {
|
||||
String unit = "繁體繁體繁體";
|
||||
String department = "A研B发C";
|
||||
String name = "12李34四56";
|
||||
|
||||
byte[] unitBytes = textToBitmapBytes(unit);
|
||||
byte[] deptBytes = textToBitmapBytes(department);
|
||||
byte[] nameBytes = textToBitmapBytes(name);
|
||||
|
||||
generateCFile(unitBytes, deptBytes, nameBytes, "display_data.c");
|
||||
|
||||
// 生成预览图片
|
||||
generatePreviewImage(unitBytes, "unit_image.png");
|
||||
generatePreviewImage(deptBytes, "department_image.png");
|
||||
generatePreviewImage(nameBytes, "name_image.png");
|
||||
}
|
||||
|
||||
// 专门生成预览图片的方法
|
||||
private static void generatePreviewImage(byte[] data, String filename) {
|
||||
try {
|
||||
BufferedImage image = convertByteArrayToImage(data, 12);
|
||||
ImageIO.write(image, "PNG", new File(filename));
|
||||
System.out.println("成功生成预览图片: " + filename);
|
||||
} catch (IOException e) {
|
||||
System.err.println("图片生成失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 可靠的字节数组转图像方法
|
||||
*/
|
||||
public static BufferedImage convertByteArrayToImage(byte[] data, int height) {
|
||||
if (data == null || data.length == 0) {
|
||||
return new BufferedImage(1, 1, BufferedImage.TYPE_INT_RGB);
|
||||
}
|
||||
|
||||
// 计算宽度
|
||||
int width = (data.length * 8) / height;
|
||||
|
||||
// 创建RGB图像
|
||||
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
|
||||
|
||||
// 设置白色背景
|
||||
for (int y = 0; y < height; y++) {
|
||||
for (int x = 0; x < width; x++) {
|
||||
image.setRGB(x, y, Color.WHITE.getRGB());
|
||||
}
|
||||
}
|
||||
|
||||
// 直接设置像素点
|
||||
int bitIndex = 0;
|
||||
for (int i = 0; i < data.length; i++) {
|
||||
int value = data[i] & 0xFF;
|
||||
for (int bit = 7; bit >= 0; bit--) { // 高位在前
|
||||
boolean isBlack = ((value >> bit) & 1) == 1;
|
||||
if (isBlack) {
|
||||
int x = bitIndex % width;
|
||||
int y = bitIndex / width;
|
||||
if (y < height) { // 确保不越界
|
||||
image.setRGB(x, y, Color.BLACK.getRGB());
|
||||
}
|
||||
}
|
||||
bitIndex++;
|
||||
}
|
||||
}
|
||||
|
||||
return image;
|
||||
}
|
||||
|
||||
/**
|
||||
* 可靠的点阵生成方法
|
||||
*/
|
||||
public static byte[] textToBitmapBytes(String text) {
|
||||
if (text == null || text.isEmpty()) {
|
||||
return new byte[0];
|
||||
}
|
||||
|
||||
// 1. 创建画布 - 使用RGB类型避免问题
|
||||
Font font = new Font("宋体", Font.PLAIN, 12);
|
||||
BufferedImage image = createTextImage(text, font);
|
||||
|
||||
// 2. 直接提取点阵数据
|
||||
return extractBitmapData(image);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建文本图像 - 最可靠的方法
|
||||
*/
|
||||
private static BufferedImage createTextImage(String text, Font font) {
|
||||
// 创建临时图像获取字体度量
|
||||
BufferedImage tempImage = new BufferedImage(1, 1, BufferedImage.TYPE_INT_RGB);
|
||||
Graphics2D tempG = tempImage.createGraphics();
|
||||
tempG.setFont(font);
|
||||
FontMetrics metrics = tempG.getFontMetrics();
|
||||
|
||||
// 计算文本宽度
|
||||
int totalWidth = metrics.stringWidth(text);
|
||||
|
||||
// 宽度对齐到8的倍数
|
||||
int width = (int) Math.ceil(totalWidth / 8.0) * 8;
|
||||
int height = 12;
|
||||
|
||||
// 创建RGB图像
|
||||
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
|
||||
Graphics2D g = image.createGraphics();
|
||||
|
||||
// 设置白色背景
|
||||
g.setColor(Color.WHITE);
|
||||
g.fillRect(0, 0, width, height);
|
||||
|
||||
// 设置黑色文本
|
||||
g.setColor(Color.BLACK);
|
||||
g.setFont(font);
|
||||
|
||||
// 关闭抗锯齿 - 确保清晰度
|
||||
g.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
|
||||
RenderingHints.VALUE_TEXT_ANTIALIAS_OFF);
|
||||
|
||||
// 绘制文本 - 精确居中
|
||||
int y = (height - metrics.getHeight()) / 2 + metrics.getAscent();
|
||||
g.drawString(text, 0, y);
|
||||
|
||||
g.dispose();
|
||||
tempG.dispose();
|
||||
|
||||
return image;
|
||||
}
|
||||
|
||||
/**
|
||||
* 提取点阵数据 - 简单可靠的方法
|
||||
*/
|
||||
private static byte[] extractBitmapData(BufferedImage image) {
|
||||
int width = image.getWidth();
|
||||
int height = image.getHeight();
|
||||
|
||||
List<Byte> byteList = new ArrayList<>();
|
||||
int currentByte = 0;
|
||||
int bitCount = 0;
|
||||
|
||||
for (int y = 0; y < height; y++) {
|
||||
for (int x = 0; x < width; x++) {
|
||||
// 获取像素颜色
|
||||
Color color = new Color(image.getRGB(x, y));
|
||||
|
||||
// 简单阈值判断 - 灰度小于128为黑色
|
||||
int gray = (color.getRed() + color.getGreen() + color.getBlue()) / 3;
|
||||
boolean isBlack = gray < 128;
|
||||
|
||||
// 高位优先打包
|
||||
currentByte = (currentByte << 1) | (isBlack ? 1 : 0);
|
||||
bitCount++;
|
||||
|
||||
if (bitCount == 8) {
|
||||
byteList.add((byte) currentByte);
|
||||
currentByte = 0;
|
||||
bitCount = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 处理最后不满8位的部分
|
||||
if (bitCount > 0) {
|
||||
currentByte <<= (8 - bitCount);
|
||||
byteList.add((byte) currentByte);
|
||||
}
|
||||
|
||||
return byteListToArray(byteList);
|
||||
}
|
||||
|
||||
private static byte[] byteListToArray(List<Byte> byteList) {
|
||||
byte[] result = new byte[byteList.size()];
|
||||
for (int i = 0; i < byteList.size(); i++) {
|
||||
result[i] = byteList.get(i);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public static void generateCFile(byte[] unit, byte[] department, byte[] name, String filename) throws IOException {
|
||||
try (FileWriter writer = new FileWriter(filename)) {
|
||||
writer.write("/**\n");
|
||||
writer.write(" * 点阵显示数据 - 可靠生成\n");
|
||||
writer.write(" * 宽度计算公式: width = (数组长度 * 8) / 12\n");
|
||||
writer.write(" */\n\n");
|
||||
writer.write("#include <stdint.h>\n\n");
|
||||
|
||||
// 单位数据
|
||||
writer.write(String.format("// 单位: %d 字节, 宽度: %d 像素\n",
|
||||
unit.length, (unit.length * 8) / 12));
|
||||
writer.write("const uint8_t unit[] = {\n ");
|
||||
writeByteArray(writer, unit);
|
||||
writer.write("\n};\n\n");
|
||||
|
||||
// 部门数据
|
||||
writer.write(String.format("// 部门: %d 字节, 宽度: %d 像素\n",
|
||||
department.length, (department.length * 8) / 12));
|
||||
writer.write("const uint8_t department[] = {\n ");
|
||||
writeByteArray(writer, department);
|
||||
writer.write("\n};\n\n");
|
||||
|
||||
// 姓名数据
|
||||
writer.write(String.format("// 姓名: %d 字节, 宽度: %d 像素\n",
|
||||
name.length, (name.length * 8) / 12));
|
||||
writer.write("const uint8_t name[] = {\n ");
|
||||
writeByteArray(writer, name);
|
||||
writer.write("\n};\n");
|
||||
}
|
||||
}
|
||||
|
||||
private static void writeByteArray(FileWriter writer, byte[] data) throws IOException {
|
||||
for (int i = 0; i < data.length; i++) {
|
||||
int value = data[i] & 0xFF;
|
||||
writer.write(String.format("0x%02X", value));
|
||||
|
||||
if (i < data.length - 1) {
|
||||
writer.write(", ");
|
||||
if ((i + 1) % 12 == 0) writer.write("\n ");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -36,48 +36,12 @@
|
||||
d.device_imei, d.longitude, d.latitude, d.tenant_id
|
||||
</sql>
|
||||
|
||||
<!-- 分页查询设备 -->
|
||||
<select id="findAll1" resultType="com.fuyuanshen.equipment.domain.Device">
|
||||
select d.* , t.type_name
|
||||
FROM device d
|
||||
LEFT JOIN device_type t ON d.device_type = t.id
|
||||
<where>
|
||||
<!-- 时间范围等其他条件保持原样 -->
|
||||
<if test="criteria.deviceName != null and criteria.deviceName.trim() != ''">
|
||||
and d.device_name like concat('%', TRIM(#{criteria.deviceName}), '%')
|
||||
</if>
|
||||
<if test="criteria.deviceMac != null">
|
||||
and d.device_mac = #{criteria.deviceMac}
|
||||
</if>
|
||||
<if test="criteria.deviceImei != null">
|
||||
and d.device_imei = #{criteria.deviceImei}
|
||||
</if>
|
||||
<if test="criteria.deviceType != null">
|
||||
and d.device_type = #{criteria.deviceType}
|
||||
</if>
|
||||
<if test="criteria.deviceStatus != null">
|
||||
and d.device_status = #{criteria.deviceStatus}
|
||||
</if>
|
||||
<if test="criteria.currentOwnerId != null">
|
||||
and d.current_owner_id = #{criteria.currentOwnerId}
|
||||
</if>
|
||||
<if test="criteria.params.beginTime != null and criteria.params.endTime != null">
|
||||
and d.create_time between #{criteria.params.beginTime} and #{criteria.params.endTime}
|
||||
</if>
|
||||
<if test="criteria.tenantId != null">
|
||||
AND tenant_id = #{criteria.tenantId}
|
||||
</if>
|
||||
</where>
|
||||
order by d.create_time desc
|
||||
</select>
|
||||
|
||||
|
||||
<!-- 分页查询设备 -->
|
||||
<select id="findAll" resultType="com.fuyuanshen.equipment.domain.Device">
|
||||
select
|
||||
da.id AS id,d.device_name,d.bluetooth_name,
|
||||
d.device_pic, d.device_mac, d.device_sn, d.update_by,d.device_imei,
|
||||
d.update_time, dg.id AS device_type, d.remark, d.binding_status,d.type_name AS typeName,
|
||||
d.update_time, dg.id AS device_type, d.remark, d.binding_status,t.type_name AS typeName,
|
||||
da.assignee_id AS customerId, da.assignee_name AS customerName, da.active AS deviceStatus,
|
||||
da.create_time AS create_time , da.assigner_name AS createByName , da.id AS assignId
|
||||
from device d
|
||||
@ -166,7 +130,7 @@
|
||||
|
||||
<!-- 查询APP绑定设备列表 -->
|
||||
<select id="queryAppBindDeviceList" resultType="com.fuyuanshen.equipment.domain.vo.AppDeviceVo">
|
||||
select d.id, d.device_name, d.device_name,
|
||||
select d.id, d.device_name, d.device_name,
|
||||
d.device_name,
|
||||
d.device_mac,
|
||||
d.device_sn,
|
||||
|
8
pom.xml
8
pom.xml
@ -37,6 +37,8 @@
|
||||
<lombok.version>1.18.36</lombok.version>
|
||||
<bouncycastle.version>1.80</bouncycastle.version>
|
||||
<justauth.version>1.16.7</justauth.version>
|
||||
|
||||
<javacv-platform.version>1.5.7</javacv-platform.version>
|
||||
<!-- 离线IP地址定位库 -->
|
||||
<ip2region.version>2.7.0</ip2region.version>
|
||||
|
||||
@ -142,6 +144,12 @@
|
||||
<version>${justauth.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.bytedeco</groupId>
|
||||
<artifactId>javacv-platform</artifactId>
|
||||
<version>${javacv-platform.version}</version> <!-- 使用合适的版本号 -->
|
||||
</dependency>
|
||||
|
||||
<!-- common 的依赖配置-->
|
||||
<dependency>
|
||||
<groupId>com.fuyuanshen</groupId>
|
||||
|
Reference in New Issue
Block a user