竞赛 AR 展厅的项目经验 后端部分 后端部分主要是学习如何优化代码,由 CRUD 搬砖转到有一定后端架构能力,还有对 Java 8 一些特性的学习
基础 接口使用Body方式传递 需要在代码前加@RequstBody,而且不能是String这样的类型
正确写法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 @PostMapping("/addFavorites") public R<String> addModelFavorites (@RequestBody Map<String, String> requestMap) { String userId = requestMap.get("userId" ); String favoritesId = requestMap.get("favoritesId" ); int type = Integer.parseInt(requestMap.get("type" )); log.info("根据id收藏模型" ); UserFavorites userFavorites = new UserFavorites (); Long userIdLong = Long.valueOf(userId); Long favoritesIdLong = Long.valueOf(favoritesId); userFavorites.setUserId(userIdLong); userFavorites.setFavoritesId(favoritesIdLong); userFavorites.setType(type); userFavoritesService.save(userFavorites); if (type == 1 ) { Model model = modelService.getById(favoritesIdLong); model.setFavoriteCount(model.getFavoriteCount() + 1 ); modelService.updateById(model); } else if (type == 2 ) { Company company = companyService.getById(favoritesIdLong); company.setFavoriteCount(company.getFavoriteCount() + 1 ); companyService.updateById(company); } else if (type == 3 ) { } return R.success("收藏模型成功" ); }
错误写法,不能用json传递,只能用http://localhost:81/model/list?categoryId=1712732535265550338&status=1的方式传递
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 @PostMapping("/addFavorites") public R<String> addModelFavorites (String userId, String favoritesId, int type) { log.info("根据id收藏模型" ); Long userIdLong = Long.valueOf(userId); Long favoritesIdLong = Long.valueOf(favoritesId); UserFavorites userFavorites = new UserFavorites (); userFavorites.setUserId(userIdLong); userFavorites.setFavoritesId(favoritesIdLong); userFavorites.setType(type); userFavoritesService.save(userFavorites); if (type == 1 ) { Model model = modelService.getById(favoritesIdLong); model.setFavoriteCount(model.getFavoriteCount() + 1 ); modelService.updateById(model); } else if (type == 2 ) { Company company = companyService.getById(favoritesIdLong); company.setFavoriteCount(company.getFavoriteCount() + 1 ); companyService.updateById(company); } else if (type == 3 ) { } return R.success("收藏模型成功" ); }
queryWrapper.in(); 使用queryWrapper.in就可以直接根据限定条件为company的id中符合ids数组中的内容,这样就不需要再根据for循环去重复存入list
1 queryWrapper.in(Company::getId, ids);
saveBatch mybatis-plus封装的一个可以批量保存的方法,参数为List类型
概念 IOC (控制反转) Inversion of Control
SpringBoot 通过 @Component 注解实现 IOC
DI (依赖注入) Dependency Injection
反射机制 1、Java反射机制的核心是在程序运行时动态加载类并获取类的详细信息,从而操作类或对象的属性和方法。本质是JVM得到class对象之后,再通过class对象进行反编译,从而获取对象的各种信息。 2、Java属于先编译再运行的语言,程序中对象的类型在编译期就确定下来了,而当程序在运行时可能需要动态加载某些类,这些类因为之前用不到,所以没有被加载到JVM。通过反射,可以在运行时动态地创建对象并调用其属性,不需要提前在编译期知道运行的对象是谁。
思想 领域驱动设计(DDD) 代码设计模式 策略模式 装饰者模式 通过套娃的方式,将需要改变的类套在外层,这样在需求更改时只需要该相应的类
命令模式 状态模型 功能实现 java上传图片到服务器并通过服务器地址访问 关于文件的二进制转化,HuTool 提供了相关的功能来简化这一常见任务:
路径拼接:
传统Java方式可能需要手动拼接路径字符串,而HuTool的FileUtil.file
方法可以更方便地构建文件对象,自动处理路径的拼接,避免手动拼接路径字符串的繁琐操作。
IO操作:
传统Java方式中,可能需要手动处理文件的输入流和输出流,并进行逐字节或逐块的复制。而使用HuTool的IoUtil.copy
方法,可以更简单地实现文件的拷贝操作,提高了代码的简洁性。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 @GetMapping("/downloadGLB") public void downloadGLB (String name, HttpServletResponse response) { try { String basePath = "C:\\model\\AndroidEntry\\" + name + "\\" ; File file = FileUtil.file(basePath, name + ".glb" ); response.setContentType("application/octet-stream" ); response.setHeader("Content-Disposition" , "attachment; filename=" + name + ".glb" ); try (InputStream inputStream = new FileInputStream (file); OutputStream outputStream = response.getOutputStream()) { byte [] buffer = new byte [4096 ]; int bytesRead; while ((bytesRead = inputStream.read(buffer)) != -1 ) { outputStream.write(buffer, 0 , bytesRead); } } IoUtil.copy(new FileInputStream (file), response.getOutputStream()); System.out.println("Downloading file: " + file.getAbsolutePath()); System.out.println("File size: " + file.length()); } catch (Exception e) { e.printStackTrace(); } }
密码加盐
取用户输入的密码:
1 String password = user.getPassword();
查询数据库中对应用户名的用户信息:
1 2 3 LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper <>(); queryWrapper.eq(User::getUsername, user.getUsername()); User emp = userService.getOne(queryWrapper);
获取数据库中存储的盐值:
1 String salt = emp.getSalt();
设置盐值到加密工具类中:
1 PasswordWithSaltUtils.setSalt(salt);
对用户输入的密码进行盐值加密:
1 String hashPassword = PasswordWithSaltUtils.hashPassword(password);
比较数据库中存储的加密密码和用户输入的加密密码是否一致,如果不一致则返回密码错误信息:
1 2 3 if (!emp.getPassword().equals(hashPassword)) { return R.error("密码错误" ); }
密码加盐封装的代码 PasswordWithSaltUtils 如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 public class PasswordWithSaltUtils { @Getter private static String salt; public static void setSalt (String salt) { if (salt == null ){ PasswordWithSaltUtils.salt = UUID.randomUUID().toString(); }else { PasswordWithSaltUtils.salt = salt; } } public static String hashPassword (String password) { try { MessageDigest mDigest = MessageDigest.getInstance("SHA-512" ); byte [] result = mDigest.digest((password + salt).getBytes()); return bytesToHex(result); } catch (NoSuchAlgorithmException e) { e.printStackTrace(); return null ; } } private static String bytesToHex (byte [] hash) { StringBuilder hexString = new StringBuilder (2 * hash.length); for (int i = 0 ; i < hash.length; i++) { String hex = Integer.toHexString(0xff & hash[i]); if (hex.length() == 1 ) { hexString.append('0' ); } hexString.append(hex); } return hexString.toString(); } }
代码优化 foreach 1 2 3 4 5 6 7 8 9 10 11 Set<Long> ids = new HashSet <>(); for (ExhibitionCompany exhibitionCompanyItem : exhibitionCompanyList) { Long id = exhibitionCompanyItem.getCompanyId(); ids.add(id); } exhibitionCompanyList.forEach((exhibitionCompany) -> { ids.add(exhibitionCompany.getId()); });
stream流 可以更方便的对集合或者数组进行链状流式的操作
1 2 3 4 5 6 7 8 9 10 List<Long> companyIds = new ArrayList <>(); for (CompanyModel companyModel1 : companyModelList) { companyIds.add(companyModel1.getCompanyId()); } List<Long> companyIds = companyModelList.stream() .map(CompanyModel::getCompanyId) .collect(Collectors.toList());
flatMap
flatMap()可以把一个对象转换为多个对象放到流中
例如下面的listByCompanyId返回的就是list
1 2 3 List<CompanyModel> companyModelList =companyIds.stream() .flatMap(idItem->companyModelService.listByCompanyId(String.valueOf(idItem)).stream()) .toList();
Optional 使用Optional类可以避免手动的null检查,使代码更加简洁
Optional.orElseGet()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 Long categoryId = item.getCategoryId();Category category = categoryService.getById(categoryId);if (category != null ) { String categoryName = category.getName(); modelDto.setCategoryName(categoryName); } else { modelDto.setCategoryName("暂无分类" ); } Long categoryId = item.getCategoryId();String categoryName = Optional.ofNullable(categoryService.getById(categoryId)) .map(Category::getName) .orElse("暂无分类" ); modelDto.setCategoryName(categoryName);
Optional.orElseThrow()
1 2 3 4 5 6 7 8 9 10 11 12 13 if (category != null ) { categoryName = category.getName(); } else { return R.error("不存在该公司分类" ); } public String getNameError (String name,String msg) { return Optional.ofNullable(name) .orElseThrow(() -> new CustomException (msg)); } String categoryName=optionalUtils.getNameError(category.getName(),"不存在该公司分类" );
养成使用Optional的习惯可以写出更加优雅的代码来避免空指针异常。
Optional.ofNullable() 将对象封装为Optional对象。无论传入的参数是否为null都不会出现问题。(建议使用 )
Optional.of() 传入的参数必须不能为null。(不建议使用)
Optional.empty() 返回一个空的Optional对象。
Optional.ifPresent() 该方法会判断其内部封装的数据是否为空,不为空的时候才能执行具体的消费代码。
Optional.isPresent() 该方法会判断其内部封装的数据是否为空,为空返回false,不为空返回true.
Optional.filter() 在方法中进行逻辑判断,如果满足会返回Optional对象;不满足则返回null.
Optional.map() 将对象中的值转为Optional<List<T>>对象.
如果想要安全的获取Optional对象中的值,不推荐使用get()方法。推荐使用以下几种方法。
Optional.orElseGet() 如果Optional中的值为null,可以自定义返回一个对象。
Optional.orElseThrow() 如果Optional中的值为null,可以手动抛出异常。
有个会混淆的点在于orElseGet与orElse的区别,先说结论orElseGet会用的更多,因为orElse无论Optional的值是否为null都会进行,会导致内存的多余使用
链接查看:java中orElse()和orElseGet()的区别_java orelseget-CSDN博客
不返回实体类的某个属性 使用@JsonProperty(access = JsonProperty.Access.WRITE_ONLY)来实现只写不返回
原文连接:java实体类,注解设置某些属性不返回前端-CSDN博客
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 @Data public class User implements Serializable { private static final long serialVersionUID = 1L ; private Long id; private String username; private String name; @JsonProperty(access = JsonProperty.Access.WRITE_ONLY) private String password; }
优化数组存入 直接在实例化时存入数组
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 List<CompanyModel> companyModelList = new ArrayList <>(); QueryWrapper<CompanyModel> modelQueryWrapper = new QueryWrapper <>(); modelQueryWrapper.in("company_id" , ids); modelQueryWrapper.eq("category_name" , categoryName); List<CompanyModel> companyModelList1 = companyModelService.list(modelQueryWrapper); if (companyModelList1.isEmpty()) { return R.error("该分类下不存在模型" ); } List<CompanyModel> companyModelList = new ArrayList <>(companyModelList1);
Objects.requireNonNullElse 1 2 3 4 5 6 7 8 9 10 else { queryWrapper.eq(Model::getCreateUserId, Objects.requireNonNullElse(admId, "0" )); }
Switch 使用箭头的方法更简单易懂
1 2 3 4 5 6 7 switch (chosenDate) { case "today" -> { } case "last7days" ->{ }
自定义异常类 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 public class CustomException extends RuntimeException { private ErrorCodeEnum errorCode; public CustomException (ErrorCodeEnum errorCode) { super (errorCode.getMessage()); this .errorCode = errorCode; } public ErrorCodeEnum getErrorCode () { return errorCode; } }
统一异常处理类 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 @ControllerAdvice(annotations = { RestController.class, Controller.class }) @ResponseBody @Slf4j public class GlobalExceptionHandler { @ExceptionHandler(SQLIntegrityConstraintViolationException.class) public R<String> exceptionHandler (SQLIntegrityConstraintViolationException ex) { log.error(ex.getMessage()); if (ex.getMessage().contains("Duplicate entry" )) { String[] split = ex.getMessage().split(" " ); String msg = split[2 ] + "已存在" ; return R.error(msg); } return R.error("未知错误" ); } @ExceptionHandler(CustomException.class) public R<String> exceptionHandler (CustomException ex) { log.error(ex.getMessage()); return R.error(ex.getMessage()); } @ExceptionHandler(CustomException.class) public R<String> exceptionHandler (CustomException ex) { log.error(ex.getMessage()); R<String> response = R.error(ex.getMessage()); ErrorCodeEnum errorCode = ex.getErrorCode(); response.setCode(errorCode.getCode()); return response; } }
抛出异常的方法 1 2 3 4 public <T> T getError (T value, ErrorCodeEnum errorCodeEnum) { return Optional.ofNullable(value) .orElseThrow(() -> new CustomException (errorCodeEnum)); }
枚举异常错误类 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 @Getter @AllArgsConstructor public enum ErrorCodeEnum { EXHIBITION_NULL(0 ,"不存在该展厅" ), CATEGORY_NULL(0 ,"不存在该分类" ), bannerModel_Null(0 ,"该展厅不存在推荐模型" ), COMPANY_ALREADY_ASSOCIATED(0 ,"已经关联公司,不能删除" ), MODEL_ALREADY_ASSOCIATED(0 ,"已经关联模型,不能删除" ); private final Integer code; private final String message; }
规范的日志打印 参考文章:Java:如果优雅地打印出完美日志-CSDN博客
方法的进入参数以及方法结束时返回值
这部分我使用 AOP 进行封装了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 @Aspect @Component @Slf4j public class LoggingAspect { @Before("@annotation(Loggable)") public void logBefore (JoinPoint joinPoint) { Signature signature = joinPoint.getSignature(); String methodName = signature.toShortString(); Object[] args = joinPoint.getArgs(); MethodSignature methodSignature = (MethodSignature) signature; String[] parameterNames = methodSignature.getParameterNames(); Map<String, Object> paramMap = new LinkedHashMap <>(); for (int i = 0 ; i < args.length; i++) { paramMap.put(parameterNames[i], args[i]); } log.info("进入 {} 方法,传入值: {}" , methodName, paramMap); } @AfterReturning(pointcut = "@annotation(Loggable)", returning = "result") public void logAfterReturning (JoinPoint joinPoint, Object result) { log.info("结束 {} 方法,返回值: {}" , joinPoint.getSignature().toShortString(), result); } }
if-else分支
1 log.info("进入 companyId 为 null 的分支" );
关键部分(可能引发错误的部分)
1 log.info("通过 exhibitionCompanyList 获取的 companyIds:{}" ,companyIds);
mybatisPlus属性自动填充 使用MetaObjectHandler来实现
下面是AR项目所写的代码
自建的 MyMetaObjectHandler 类并继承 mp MetaObjectHandler 接口重写相关方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 @Component @Slf4j public class MyMetaObjectHandler implements MetaObjectHandler { @Override public void insertFill (MetaObject metaObject) { log.info("公共字段自动填充【insert】。。。" ); log.info(metaObject.toString()); metaObject.setValue("createTime" , LocalDateTime.now()); metaObject.setValue("updateTime" , LocalDateTime.now()); metaObject.setValue("createUser" ,new Long (1 )); metaObject.setValue("updateUser" ,new Long (1 )); } @Override public void updateFill (MetaObject metaObject) { log.info("公共字段自动填充【update】。。。" ); log.info(metaObject.toString()); metaObject.setValue("updateTime" ,LocalDateTime.now()); metaObject.setValue("updateUser" ,new Long (1 )); } }
同时实体类也需要进行相关的注解配置
1 2 3 4 5 @TableField(fill = FieldFill.INSERT) private LocalDateTime createTime;@TableField(fill = FieldFill.INSERT_UPDATE) private LocalDateTime updateTime;
AOP AOP:面向切面编程,对面向对象编程的一种补充,一般处理非业务代码,比如打印日志
比如说每一个对象都需要开头结尾打印日志,那么可以把每个对象切一刀,再把切面抽象为对象,就可以实现封装每次开头结尾打印日志的效果
首先是自定义一个接口,这样就可以自由控制在哪个方法进行日志打印
自定义接口:Loggable
1 2 3 4 @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface Loggable {}
然后定义一个切面类,在切面类里写好需要封装的日志
切面类:LoggingAspect
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 @Aspect @Component @Slf4j public class LoggingAspect { @Before("@annotation(Loggable)") public void logBefore (JoinPoint joinPoint) { Signature signature = joinPoint.getSignature(); String methodName = signature.toShortString(); Object[] args = joinPoint.getArgs(); MethodSignature methodSignature = (MethodSignature) signature; String[] parameterNames = methodSignature.getParameterNames(); Map<String, Object> paramMap = new LinkedHashMap <>(); for (int i = 0 ; i < args.length; i++) { paramMap.put(parameterNames[i], args[i]); } log.info("进入 {} 方法,参数: {}" , methodName, paramMap); } @AfterReturning(pointcut = "@annotation(Loggable)", returning = "result") public void logAfterReturning (JoinPoint joinPoint, Object result) { log.info("结束 {} 方法,返回值: {}" , joinPoint.getSignature().toShortString(), result); } }
最后是使用方法
在需要打印日志的方法上面加上注解@Loggable
1 2 3 4 5 6 @GetMapping("/getAll") @Loggable public R<List<ExhibitionCompany>> listAllCompany (String exhibitionId) { List<ExhibitionCompany> list = exhibitionCompanyService.listByExhibitionId(exhibitionId); return R.success(list); }
异步请求 参考文章:这8种java异步实现方式,性能炸裂!_Java精选的博客-CSDN博客
消息队列(MQ) 参考文章:java常用的消息队列 看完这篇你就懂了_java给外部推送消息队列都需要做什么-CSDN博客
主流框架有RabbitMQ、RocketMQ、ActiveMQ、Kafka、ZeroMQ、Pulsar
目前使用阿里开发的 RocketMQ 较多
框架学习 JPA 和mybatis-plus实在太像了,唯一不同的是dao(数据访问对象 data access object)在JPA中叫做repository
,而mybaits的dao叫mapper
,以及没有IService直接为service提供了基础的增删改查,需要自己写对应的接口,并借助repository来实现
基础链接:最详细的Spring-data-jpa入门(一)_springdatajpa-CSDN博客
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 @Service public class JpaUserServiceImpl implements JpaUserService { @Resource private JpaUserRepository jpaUserRepository; @Override public JpaUser insertUser (JpaUser user) { return jpaUserRepository.save(user); } @Override public void deleteUser (Long id) { jpaUserRepository.deleteById(id); } @Override public JpaUser updateUser (JpaUser user) { return jpaUserRepository.save(user); } @Override public List<JpaUser> findAllUser () { return jpaUserRepository.findAll(); } @Override public JpaUser findUserById (Long id) { return jpaUserRepository.findById(id).orElse(null ); } }
Sa-Token 1. 添加依赖 Maven 方式
1 2 3 4 5 <dependency > <groupId > cn.dev33</groupId > <artifactId > sa-token-spring-boot3-starter</artifactId > <version > 1.36.0</version > </dependency >
2. 设置配置文件 application.yml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 server: port: 81 sa-token: token-name: satoken timeout: 2592000 active-timeout: -1 is-concurrent: false is-share: false token-style: uuid is-log: true
3. 登录认证以及获取 token 登录时使用:login
获取 token 使用:getTokenValueByLoginId
1 2 3 4 5 6 7 8 9 10 11 @PostMapping("/login") public R<User> login (HttpServletRequest request, @RequestBody User user) { StpUtil.login(emp.getId()); emp.setToken(StpUtil.getTokenValueByLoginId(emp.getId())); return R.success(emp); }
4. 获取 userId 获取 userid 使用 getLoginIdByToken
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 @GetMapping("/favoritesModels") @Loggable public R<List<ModelDto>> favoritesModels (String token) { Long userIdLong = Long.valueOf((String) optionalUtils.getError(StpUtil.getLoginIdByToken(token), ErrorCodeEnum.INVALID_TOKEN)); log.info("开始获取收藏的模型..." ); Long userIdLong = Long.valueOf((String)StpUtil.getLoginIdByToken(token); return R.success(modelDtoList); }
Bug复盘 循环依赖 好像这块是八股文的部分,但是实际开发还是遇到这个问题了,所以还是记录一下
循环依赖其实就是循环引用,也就是两个或则两个以上的bean互相持有对方,最终形成闭环。比如A依赖于B,B依赖于C,C又依赖于A。
解决方法:
加上Lazy注解,会延时引用bean,虽然这个解决方法,但是感觉好像有点头疼砍头,后续出问题了,再整理一下依赖关系吧
1 2 3 @Autowired @Lazy private CompanyService companyService;
前端部分 基础 JS基础 符号 == 判断有误的问题 可以使用 = = = 来实现,同样java中可以.isEmpty、== “”、== null都试试
axios封装API 1、下载 axios 插件
2、main.js 引入依赖
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 import Vue from 'vue' import './plugins/element-ui/index.css' import App from './App.vue' import store from './store' import router from './router' import axios from 'axios' ;Vue .config .productionTip = false new Vue ({ store, router, axios, ElementUI , render : h => h (App ) }).$mount('#app' )
3、封装请求方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 import axios from 'axios' ;import ElementUI from 'element-ui' ;import 'element-ui/lib/theme-chalk/index.css' ;import Vue from 'vue' ;import router from '../router/index' ; Vue .use (ElementUI );const service = axios.create ({ baseURL : 'api' , timeout : 1000000 }); service.interceptors .request .use ( (config ) => { config.headers ['Content-Type' ] = 'application/json;charset=utf-8' ; if (config.method === 'get' && config.params ) { let url = config.url + '?' ; for (const propName of Object .keys (config.params )) { const value = config.params [propName]; var part = encodeURIComponent (propName) + '=' ; if (value !== null && typeof value !== 'undefined' ) { if (typeof value === 'object' ) { for (const key of Object .keys (value)) { let params = propName + '[' + key + ']' ; var subPart = encodeURIComponent (params) + '=' ; url += subPart + encodeURIComponent (value[key]) + '&' ; } } else { url += part + encodeURIComponent (value) + '&' ; } } } url = url.slice (0 , -1 ); config.params = {}; config.url = url; } return config; }, (error ) => { console .log (error); return Promise .reject (error); } ); service.interceptors .response .use ( (response ) => { console .log ('---响应拦截器---' , response); if (response.data && response.data .code ) { const code = response.data .code ; const msg = response.data .msg ; if (code === 0 && msg === 'You have to login' ) { console .log ('---/backend/page/login/login.html---' , code); localStorage .removeItem ('userInfo' ); router.push ({ name : 'login' }); } else { return response.data ; } } else { return response; } }, (error ) => { console .log ('err' + error); let { message } = error; if (message === 'Network Error' ) { message = '后端接口连接异常' ; } else if (message.includes ('timeout' )) { message = '系统接口请求超时' ; } else if (message.includes ('Request failed with status code' )) { message = '系统接口' + message.substr (message.length - 3 ) + '异常' ; } ElementUI .Message ({ message : message, type : 'error' , duration : 5 * 1000 }); return Promise .reject (error); } ); export default service;
4、封装 api
注意传递参数有所不同,GET请求通常使用查询字符串(params)传递参数,而POST、PUT请求通常使用请求体(data)传递参数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 import $axios from '../utils/request' ;export const getCompanyPage = (params ) => { return $axios({ url : '/company/page' , method : 'get' , params }) } export const deleteCompany = (ids ) => { return $axios({ url : '/company' , method : 'delete' , params : { ids } }) } export const editCompany = (params ) => { return $axios({ url : '/company' , method : 'put' , data : { ...params } }) } export const addCompany = (params ) => { return $axios({ url : '/company' , method : 'post' , data : { ...params } }) } export const queryCompanyById = (id ) => { return $axios({ url : `/company/${id} ` , method : 'get' }) } export const queryCompanyList = (params ) => { return $axios({ url : '/company/listCompanies' , method : 'get' , params }) } export const companyStatusByStatus = (params ) => { return $axios({ url : `/company/status/${params.status} ` , method : 'post' , params : { ids : params.ids } }) }
5、使用方法
需要 import 导入
然后注意参数的传入有不同方法
比如直接建一个对象 params 然后传入getCompanyPage(params) 或者直接在deleteCompany(id)
还有注意具体的使用
比如 getCompanyPage 就直接接收就行 const res = await getCompanyPage(params);
而 deleteCompany会加入 then 以及箭头函数来进行后续操作
deleteCompany(type === ‘批量’ ? this.checkList.join(‘,’) : id)
.then(res => {
})
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 <template> <div class="dashboard-container" id="company-app"> <div class="container"> <!-- ... 省略部分布局代码 ... --> <el-table :data="tableData" stripe class="tableBox" @selection-change="handleSelectionChange"> <el-table-column type="selection" width="25"></el-table-column> <el-table-column prop="name" label="公司名称"></el-table-column> <!-- ... 省略部分列配置 ... --> <el-table-column label="操作" width="160" align="center" v-if="userInfo.position == 'manager' || userInfo.position == 'admin'"> <template slot-scope="scope"> <el-button type="text" size="small" class="blueBug" @click="addSetMeal(scope.row.id)"> 修改 </el-button> <el-button type="text" size="small" class="blueBug" @click="statusHandle(scope.row)"> {{ scope.row.status == '0' ? '展示' : '隐藏' }} </el-button> <el-button type="text" size="small" class="delBut non" @click="deleteHandle('单删', scope.row.id)"> 删除 </el-button> </template> </el-table-column> </el-table> <el-pagination class="pageList" :page-sizes="[10, 20, 30, 40]" :page-size="pageSize" layout="total, sizes, prev, pager, next, jumper" :total="counts" @size-change="handleSizeChange" :current-page.sync="page" @current-change="handleCurrentChange"></el-pagination> </div> </div> </template> <script> import { getCompanyPage, deleteCompany } from '../../api/company.js'; export default { // ... 其他部分保持不变 ... methods: { async init() { const params = { page: this.page, pageSize: this.pageSize, name: this.input ? this.input : undefined, }; try { const res = await getCompanyPage(params); if (String(res.code) === '1') { if (res.data != null) { this.tableData = res.data.records || []; // 遍历tableData中的每个记录 this.tableData.forEach(record => { if (record.description === "") { record.description = "暂无简介"; } }); this.tableData.forEach(record => { console.log("Description: " + record.description); }); if (this.tableData.length > 0) { this.ifNull = false } this.counts = res.data.total; } } } catch (err) { this.$message.error('请求出错了:' + err); } }, // 删除 deleteHandle(type, id) { if (type === '批量' && id === null) { if (this.checkList.length === 0) { return this.$message.error('请选择删除对象'); } } this.$confirm('确定删除该公司, 是否继续?', '确定删除', { 'confirmButtonText': '确定', 'cancelButtonText': '取消', }).then(() => { deleteCompany(type === '批量' ? this.checkList.join(',') : id) .then(res => { if (res.code === 1) { this.$message.success('删除成功!'); this.handleQuery(); } else { this.$message.error(res.data.msg || '公司仍在展示状态'); } }) .catch(err => { this.$message.error('请求出错了:' + err); }); }).catch(() => { // 用户点击取消按钮的处理逻辑 // 可以不做任何处理,或者在这里添加一些取消操作的逻辑 }); }, // ... 其他方法保持不变 ... }, }; </script>
js字符串处理 1 2 let fileName=file.name this .ruleForm .modelPath = fileName.substring (0 , fileName.lastIndexOf ("." ));
js判断数组为空 不能使用 == [] 或者 == “” 或者 == null 以及三个等号也不行,需要使用 .length==0
js数组处理常用方法 push() 末尾添加数据 作用: 就是往数组末尾添加数据
返回值: 就是这个数组的长度
1 2 3 4 var arr = [10 , 20 , 30 , 40 ]res = arr.push (20 ) console .log (arr);console .log (res);
splice() 截取数组 它仅能够截取数组中指定区段的元素,并返回这个子数组。
如果仅指定一个参数,则表示从该参数值指定的下标位置开始,截取到数组的尾部所有元素
1 2 3 4 var parts = routerPath.split ('/' ); var result = parts.length >= 3 ? parts[2 ] : null ;console .log ("result:" + result);
join() 数组转字符串 作用: 就是把一个数组转成字符串
返回值: 就是转好的一个字符串
1 2 3 4 var arr = [10 , 20 , 10 , 30 , 40 , 50 , 60 ]res = arr.join ("+" ) console .log (arr)console .log (res);
map 映射数组 map() 方法返回一个新数组,这个新数组:由原数组中的每个元素调用一个指定方法后的返回值组成的新数组。
map() 不会对空数组进行检测。
map() 不会改变原始数组。
1 2 3 4 5 6 7 8 9 10 let data = [1 ,2 ,3 ,4 ,5 ];let newData = data.map (function (item ){ return item * item; }); console .log (newData);let newData2 = data.map (item => item *item);console .log (newData2);
filter 过滤数组 filter用于对数组进行过滤 。 它创建一个新数组,新数组中的元素是通过检查指定数组中符合条件的所有元素。
1 2 3 4 5 6 7 let nums = [1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , 10 ]; let res = nums.filter ((num ) => { return num > 5 ; }); console .log (res);
find()用来获取数组中满足条件的第一个数据 获取数组中满足条件的第一个数据,最后会返回一个数组
1 const menu = this .menuList .find ((item ) => item.id === routerPath);
ES6核心语法 1、变量与常量
1 2 3 4 5 6 7 8 9 10 11 12 var count : number =0 let count : number =0 const BASE_URL : string ="http:..." { let count : number =0 count++ } console .log (count)
2、模版字符串
1 2 3 4 5 const str1 : string ='abc' +'efg' const str2 : string = `efg${str1} 这也是字符串的内容 `
3、解构赋值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 const arr : number [] = [1 ,2 ,3 ]const a=arr[0 ]const b=arr[1 ]const c=arr[2 ]const [a, b, c]: number [] = [1 , 2 , 3 ];const user : { username : string , age : number , gender : string } = { username : 'TEC' , age : 18 , gender : 'male' }; const { username, age, gender }: { username : string , age : number , gender : string } = { username : 'TEC' , age : 18 , gender : 'male' };
4、数组和对象的扩展
4.1、扩展运算符
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 const arr1 = [1 , 2 ,3 ]const arr2 = [4 , 5 , 6 ]const arr3 = [...arr1, ...arr2, 10 , 2 ]const obj1={ a :1 } const obj2={ b :1 } const obj3={ name :'TEC' , ...obj1, ...obj2 }
4.2、数组方法
1 2 3 4 5 6 7 function fn (...args ) { console .log (args); } fn (1 , 2 , 3 , 4 );
4.3、对象方法
1 2 3 4 5 6 const objA={ name : 'TEC' , age : 18 } const objB=Object .assign ({},objA)
5、 Class
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 class A { name: string; age: number; constructor(name: string, age: number) { this .name = name; this .age = age; } introduce() { console.log(`My name is ${this .name} and I am ${this .age} years old.`); } } const a1 = new A ("吴悠" , 18 );a1.introduce(); class B extends A { additionalProperty: string; constructor(name: string, age: number, additionalProperty: string) { super (name, age); this .additionalProperty = additionalProperty; } introduceWithAdditional() { console.log(`My name is ${this .name}, I am ${this .age} years old, and my additional property is ${this .additionalProperty}.`); } } const b1 = new B ("张三" , 25 , "Some additional info" );b1.introduceWithAdditional();
6、箭头函数
1 2 3 4 5 6 7 8 9 10 11 12 13 const getSum1 = (n ) => n + 3 ;const getSum2 = (n1, n2 ) => n1 + n2;const getSum3 = (n1, n2, ...other ) => console .log (n1, n2, other);const getResult = (arr ) => { let sum = 0 ; arr.forEach (item => sum += item); return sum; }; console .log (getResult ([1 , 2 , 3 , 4 , 5 ]));
布局基础 前端添加图标的方法 在阿里巴巴图标库中找到图标加入购物车,选完后最好加入一个项目中,因为购物车可能被清,然后点击下载,将下载下来的文件全部存到项目静态资源目录如assets,使用方法则是直接用class命名的方式,具体图标的,命名可在iconfont.json中查看,注意”css_prefix_text”: “icon-“,有这个就需要class前面加上icon-
在两个元素之间拉开一定的距离,并且两个元素加上距离依旧占到height的100% 使用flex布局中的gap可以达到这样的效果
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 <template> <div class="listView"> <div class="line3"> <!--下面的代码是实现效果的核心代码--> <div class="l3Data2" style="display: flex; gap: 10px;flex-direction: column;"> <!-- 模型喜爱数据 --> <el-card style="height: 50%"> <p class="card-header" slot="header">模型喜爱数据</p> <div id="modelPieChart" style="height: 100%;"></div> </el-card> <!-- 类型喜爱数据 --> <el-card style="height: 50%"> <p class="card-header" slot="header">类型喜爱数据</p> <div id="categoryPieChart" style="height: 100%;"></div> </el-card> </div> </div> </div> </template>
大屏适配 想要实现的效果为在任意屏幕下都能占满屏幕,使用 min-height: 100vh;来解决这个问题,这样无论数据是否超出限制,至少能够占满屏幕
接下来在数据超出时因为同一父元素下两个子元素同时使用使用了height:100%,导致可能出现两个滚动条,这也提醒了我height百分比的形式不是万能的,在内部会超出限制时会导致布局出错,百分比的形式还是应该出现在缩放时比较合理,常规不需要进行限制,只需要min-height: 100vh;然后元素会自己进行高度拉伸
Vue基础 router防止页面回退 直接使用replace代替push可以使得页面的回退消失
1 2 this .$router .replace ({ name : 'index' });
路由监听防止用户回退页面出错 通过watch的方式监听$route并进行相依的操作
1 2 3 4 5 watch : { '$route' (to, from ) { this .handleRouteChange (); }, },
框架学习 Echarts echarts折线图 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 renderLineChart ( ) { const lineChart = echarts.init (document .getElementById ('lineChart' )); const option = { xAxis : { type : 'category' , data : ['Day 1' , 'Day 2' , 'Day 3' , 'Day 4' , 'Day 5' ], }, yAxis : { type : 'value' , }, legend : { data : ['模型观看人数' ], orient : 'horizontal' , x : 'center' , y : 'top' , itemGap : 40 , itemHeight : 10 , }, series : [ { name : '模型观看人数' , data : [100 , 200 , 150 , 300 , 250 ], type : 'line' , color : ['#5DB1FF' ] }, ], }; window .addEventListener ("resize" , function ( ) { lineChart.resize (); }); lineChart.setOption (option); },
echarts饼图 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 renderModelPieChart ( ) { const modelPieChart = echarts.init (document .getElementById ('modelPieChart' )); const option = { legend : { data : ['Model A' , 'Model B' ], orient : 'horizontal' , x : 'center' , y : 'top' , itemGap : 40 , itemHeight : 10 , }, series : [ { name : '模型收藏人数' , type : 'pie' , radius : '55%' , data : [ { name : 'Model A' , value : 30 , itemStyle : { color : '#FAC858' } }, { name : 'Model B' , value : 20 , itemStyle : { color : '#91CC75' } }, ], }, ], }; window .addEventListener ("resize" , function ( ) { modelPieChart.resize (); }); modelPieChart.setOption (option); },
echarts图例 具体参考Echarts legend属性使用-CSDN博客
在option中加入legend即可
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 renderLineChart ( ) { const lineChart = echarts.init (document .getElementById ('lineChart' )); const option = { legend : { data : ['模型观看人数' ], orient : 'horizontal' , x : 'center' , y : 'top' , itemGap : 40 , itemHeight : 10 , } }; lineChart.setOption (option); },
echarts改变图标颜色 折线图的折线换颜色,直接color底下加相应颜色即可
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 renderLineChart ( ) { const lineChart = echarts.init (document .getElementById ('lineChart' )); const option = { series : [ { name : '模型观看人数' , data : [100 , 200 , 150 , 300 , 250 ], type : 'line' , color : ['#5DB1FF' ] }, ], }; lineChart.setOption (option); },
饼图换颜色需要在外层嵌套itemStyle
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 renderModelPieChart ( ) { const modelPieChart = echarts.init (document .getElementById ('modelPieChart' )); const option = { series : [ { name : '模型收藏人数' , type : 'pie' , radius : '55%' , data : [ { name : 'Model A' , value : 30 , itemStyle : { color : '#FAC858' } }, { name : 'Model B' , value : 20 , itemStyle : { color : '#91CC75' } }, ], }, ], }; modelPieChart.setOption (option); }
echarts美观折线图 圆润折线图以及折线面积效果还有增大转折点尺寸以及取消边界间隙 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 option = { xAxis : { type : 'category' , boundaryGap : false , data : ['Mon' , 'Tue' , 'Wed' , 'Thu' , 'Fri' , 'Sat' , 'Sun' ] }, yAxis : { type : 'value' }, series : [ { data : [820 , 932 , 901 , 934 , 1290 , 1330 , 1320 ], type : 'line' , smooth : true , symbolSize : 7 , areaStyle : {} } ] };
echarts不同尺寸下的优化 图表不跟随页面改变而缩放 这是因为echarts的相关图标需要resize一下才会改变大小
1 2 3 4 5 6 7 8 9 10 11 renderLineChart ( ) { const lineChart = echarts.init (document .getElementById ('lineChart' )); window .addEventListener ("resize" , function ( ) { lineChart.resize (); }); lineChart.setOption (option); },
图表不跟随横向flex的大小进行缩放 这点问题是比如说我设置flex一个为2一个为1,最后两个元素缩放却缩放成了1:2,我本来以为是echarts配置问题,但是在查找后(原文链接:echarts不会随着flex布局自适应伸缩_flex不跟随内容自动伸展_zc自由飞~的博客-CSDN博客 )我发现需要加上最小宽度,并且最小宽度还要和flex布局的大小一致
1 2 3 4 5 6 7 8 9 10 11 12 13 14 .l3Data1 { flex : 2 ; min-width : 60% ; margin-right : 20px ; margin-top : 20px ; box-sizing : border-box; } .l3Data2 { flex : 1 ; min-width : 30% ; margin-top : 20px ; box-sizing : border-box; }
图表不跟随竖直向flex的大小进行缩放 这个问题比较曲折,首先是竖直方向进行缩放,发现图标直接随着缩放大小而消失到屏幕外,后来发现原因来自于最外层添加了overflow-y: hidden;在修改为overflow-y: auto;后,出现了两个y轴点拖动条,然后发现页面里有多余的iframe并占据了4x4的大小导致最外层又多了一个拖动条,解决方法在于直接在iframe的id的css中加上display:none,但是这样图表在缩放后会变到不能看的程度,这时候需要加上min-height,这个还是挺基础的,关键在于这里并不能使用常规的百分比方式,因为会导致另外的元素与其高度不统一,下面会放上竖直方向如何进行flex缩放
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 .listView { display : flex; flex-direction : column; } .line1 { flex : 1 ; min-height : 40px ; } .line2 { flex : 10 ; min-height : 250px ; } .line3 { flex : 20 ; min-height : 460px ; }
Element-UI 修改el-card的样式 注意要加上::v-deep才能修改到样式
1 2 3 4 5 6 7 8 9 10 .el -card ::v-deep .el -card__header { height : 10 %; padding-left : 20px; } .el -card ::v-deep .el -card__body { display : grid; align-items : center; height : 90 %; }
element-ui 联排按钮效果 使用el-button-group可以去除el-button自带的右侧margin,代码的代码还实现了默认当日按钮被点击,点击其他按钮也会进行相应的点击变化
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 <template> <div class="listView"> <!-- 时间筛选功能 --> <div class="line1"> <el-button-group> <el-button :type="selectedButton === 'today' ? 'primary' : ''" @click="selectDate('today')">当日</el-button> <el-button :type="selectedButton === 'last7days' ? 'primary' : ''" @click="selectDate('last7days')">最近7天</el-button> <el-button :type="selectedButton === 'last30days' ? 'primary' : ''" @click="selectDate('last30days')">最近30天</el-button> <el-button :type="selectedButton === 'last60days' ? 'primary' : ''" @click="selectDate('last60days')">最近60天</el-button> <el-button :type="selectedButton === 'last90days' ? 'primary' : ''" @click="selectDate('last90days')">最近90天</el-button> <el-button :type="selectedButton === 'lastyear' ? 'primary' : ''" @click="selectDate('lastyear')">最近一年</el-button> </el-button-group> </div> </div> </template> <script> export default { data() { return { selectedButton: 'today', // 默认选中当日按钮 }; }, }; </script>
el-checkbox-group的值为对象时的复选框回显问题 el-checkbox-group 值不可以为对象,所以新建了一个数组checkedListIds只存入id,然后label里存入item.id
Element-ui 进度条的使用 参考链接:axios进度条功能onDownloadProgress函数total参数为undefined问题 - 知乎 (zhihu.com)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 <el-progress :percentage="this.downloadProgress" type="circle" :width="400" color="#ffc200" v-if="this.downloadProgress > 0 && this.downloadProgress < 100"></el-progress> <script> export default { data() { return { downloadProgress: 0, }; }, components: { vue3dLoader }, methods: { Preview(androidModelPath) { // ... (other code) ... // 初始化进度为0 this.downloadProgress = 0; this.glbFileUrl = ''; // 发起文件下载请求 downloadGLBPreview({ name: androidModelPath }, (progressEvent) => { // 计算下载进度 console.log('Download Progress:', progressEvent.loaded, '/', progressEvent.total); this.downloadProgress = Math.round(progressEvent.loaded / progressEvent.total * 100); }).then((url) => { // 下载完成后,设置文件URL并显示对话框 this.glbFileUrl = url; }); this.dialogVisible = true; }, }, }; </script>
封装 api 部分
注意点在于我本来是通过localhost:8080进行转发到http://42.192.90.134:81的,但是这样做的话,返回头的ContentLength就会丢失,而关键的统计文件总大小的 progressEvent.total 就来自于返回头中的 ContentLength ,所以必须直接使用http://42.192.90.134:81/common/downloadGLBPreview的方式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 import $axios from '../utils/request' ;export const downloadGLBPreview = (params, onDownloadProgress ) => { return $axios({ url : `http://42.192.90.134:81/common/downloadGLBPreview` , method : 'get' , params, responseType : 'blob' , onDownloadProgress, }).then (response => { const blob = new Blob ([response.data ], { type : 'model/gltf-binary' }); const url = window .URL .createObjectURL (blob); return url; }); };
效果实现 loading动画加载 使用v-loading来控制某个页面动画的加载
1 <div class="listView" v-loading="loading">
在js部分中使用this.loading控制动画是否出现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 <script> export default { data() { return { loading: true, }; }, methods: { async init(chosenDate) { const params = { chosenDate: chosenDate, }; try { this.loading = true const res = await getAnalytics(params); if (String(res.code) === '1') { this.closeLoading() } } catch (err) { this.$message.error('请求出错了:' + err); } }, closeLoading() { this.timer = null; this.timer = setTimeout(() => { this.loading = false; }, 600); }, } } </script>
3D 模型预览 Vue-3D-Loader 框架地址:快速上手 | Vue 3d loader (king2088.github.io)
1、安装插件
注意 vue-3d-model 的最新版本不适配 vue2 ,要使用的话就需要指定版本
vue3请安装2.0.0 及以上版本,vue2请安装1.x.x 版本
1 npm install vue-3d -loader
2、使用方法
先 import 引入 ,然后使用插件所提供的组件vue3dLoader
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 <template> <vue3dLoader v-else :width="700" :height="800" :fileType="'gltf'" :cameraPosition="{ x: 0, y: 0, z: 0.35 }" :filePath="this.glbFileUrl" :backgroundColor="0x000000" ></vue3dLoader> </template> <script> import { getModelPage, deleteModel, modelStatusByStatus, addBannerModel, removeBannerModel, downloadGLBPreview } from '../../api/model' ;import { vue3dLoader } from "vue-3d-loader" ;export default { data() { return { glbFileUrl: '' }; }, methods: { Preview(androidModelPath) { console.log("androidModelPath:" + androidModelPath) this .downloadProgress = 0 ; this .glbFileUrl = '' downloadGLBPreview({ name: androidModelPath }, (progressEvent) => { console.log('Download Progress:' , progressEvent.loaded, '/' , progressEvent.total); this .downloadProgress = Math.round(progressEvent.loaded / progressEvent.total * 100 ); }).then((url) => { this .glbFileUrl = url; }); this .dialogVisible = true }, }, }; </script>
其他部分 服务器访问图片 原文如下如何访问存在服务器的图片或者视屏等静态资源_如何访问服务器上的静态视频-CSDN博客
方法有很多,比如springboot静态资源配置来访问或者nginx反向代理,这里我选择使用启动tomcat端口来进行访问
首先启动一个tomcat服务器,宝塔管理系统有一个插件java项目管理器,可以轻松的配置tomcat的配置文件(下面的配置文件为了简约我删了很多)新加Context标签然后docBase中输入服务器放图片的位置,path中则是访问的地址,最后网址为42.192.90.134:8082/images/014896a7-bb90-40e9-a988-64703ed80e12.jpg
1 2 3 4 5 6 7 8 9 10 11 12 <Server port ="8805" shutdown ="SHUTDOWN" > <Service name ="Catalina" > <Engine name ="Catalina" defaultHost ="localhost" > <Host name ="localhost" appBase ="webapps" unpackWARs ="true" autoDeploy ="true" > <Valve className ="org.apache.catalina.valves.AccessLogValve" directory ="logs" prefix ="localhost_access_log" suffix =".txt" pattern ="%h %l %u %t " %r" %s %b" /> <Context docBase ="C:\img" path ="/images" debug ="0" reloadable ="true" /> </Host > </Engine > </Service > </Server >
解决服务器图片跨域问题 因为我访问图片的方法是在服务器端开了一个tomcat端口8082,实现方法如下: 主要是新增了docBase绑定服务器地址
1 2 3 4 5 <Host name ="localhost" appBase ="webapps" unpackWARs ="true" autoDeploy ="true" > <Valve className ="org.apache.catalina.valves.AccessLogValve" directory ="logs" prefix ="localhost_access_log" suffix =".txt" pattern ="%h %l %u %t " %r" %s %b" /> <Context docBase ="C:\img" path ="/images" debug ="0" reloadable ="true" /> </Host >
然后在访问C:\img\iphone-14-pro这类的文件时,因为基础地址不等于docBase,所以存在跨域问题,但是tomcat的跨域不可能用@CrossOrigin去实现跨域,需要配置文件,设置跨域请求头,具体配置如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 <filter > <filter-name > CorsFilter</filter-name > <filter-class > org.apache.catalina.filters.CorsFilter</filter-class > <init-param > <param-name > cors.allowed.origins</param-name > <param-value > *</param-value > </init-param > <init-param > <param-name > cors.allowed.methods</param-name > <param-value > GET,POST,HEAD,OPTIONS,PUT</param-value > </init-param > <init-param > <param-name > cors.allowed.headers</param-name > <param-value > Content-Type,X-Requested-With,accept,Origin,Access-Control-Request-Method,Access-Control-Request-Headers</param-value > </init-param > <init-param > <param-name > cors.exposed.headers</param-name > <param-value > Access-Control-Allow-Origin,Access-Control-Allow-Credentials</param-value > </init-param > <init-param > <param-name > cors.preflight.maxage</param-name > <param-value > 10</param-value > </init-param > </filter > <filter-mapping > <filter-name > CorsFilter</filter-name > <url-pattern > /*</url-pattern > </filter-mapping >
原文如下:Tomcat设置跨域 - 知乎 (zhihu.com)
MySQL索引 ![截屏2023-11-13 18.42.01](/Users/tec/Library/Application Support/typora-user-images/截屏2023-11-13 18.42.01.png)
IDEA快捷键 在方法后直接加上.var 可以自动创建相应的返回值的对象
.notnull 生成相应的 if 语句判断为非空(虽然现在基本都使用自己封装的 OptionalUtils 了)