智能图库模块-团队空间

这一期主要讲到了Sa-token鉴权框架和ShardingSphere分库分表框架的使用,这里主要记录下使用前提以及如何使用他们

Sa-Token

这次项目里新增了团队空间的需求,与之前的私有空间相比,团队空间还多了一些别的权限校验,像是空间的创建人对空间其他成员的管理(权限修改或者删除成员)、空间里的成员对图片的不同权限(有的只能浏览图片,有的可以编辑图片),像这种权限校验的场景,就可以使用RBAC模型来实现,即:用户-角色-权限这一对应关系,一个用户可以对应一个或多个角色,一个角色可以对应一个或多个权限。

前期定义

在resource目录下新建biz/spaceUserAuthConfig.json文件来定义角色和权限:

json
复制代码
{ "permissions": [ { "key": "spaceUser:manage", "name": "成员管理", "description": "管理空间成员,添加或移除成员" }, { "key": "picture:view", "name": "查看图片", "description": "查看空间中的图片内容" }, { "key": "picture:upload", "name": "上传图片", "description": "上传图片到空间中" }, { "key": "picture:edit", "name": "修改图片", "description": "编辑已上传的图片信息" }, { "key": "picture:delete", "name": "删除图片", "description": "删除空间中的图片" } ], "roles": [ { "key": "viewer", "name": "浏览者", "permissions": [ "picture:view" ], "description": "查看图片" }, { "key": "editor", "name": "编辑者", "permissions": [ "picture:view", "picture:upload", "picture:edit", "picture:delete" ], "description": "查看图片、上传图片、修改图片、删除图片" }, { "key": "admin", "name": "管理员", "permissions": [ "spaceUser:manage", "picture:view", "picture:upload", "picture:edit", "picture:delete" ], "description": "成员管理、查看图片、上传图片、修改图片、删除图片" } ] }

manage包中创建auth/model/SpaceUserAuthConfig.java文件来接收配置文件里的值:

java
复制代码
/** * 空间成员权限配置 */ @Data public class SpaceUserAuthConfig implements Serializable { /** * 权限列表 */ private List<SpaceUserPermission> permissions; /** * 角色列表 */ private List<SpaceUserRole> roles; private static final long serialVersionUID = 1L; }

auth包中创建SpaceUserAuthManager类来加载配置文件到SpaceUserAuthConfig中以及获取对应的权限列表:

java
复制代码
@Component public class SpaceUserAuthManager { public static final SpaceUserAuthConfig SPACE_USER_AUTH_CONFIG; @Resource private UserService userService; @Resource private SpaceUserService spaceUserService; static { String json = ResourceUtil.readUtf8Str("biz/spaceUserAuthConfig.json"); SPACE_USER_AUTH_CONFIG = JSONUtil.toBean(json, SpaceUserAuthConfig.class); } /** * 根据角色获取权限列表 * @param spaceUserRole * @return */ public List<String> getPermissionsByRole(String spaceUserRole){ if (StrUtil.isBlank(spaceUserRole)){ return new ArrayList<>(); } SpaceUserRole role = SPACE_USER_AUTH_CONFIG.getRoles().stream() .filter(r -> r.getKey().equals(spaceUserRole)) .findFirst() .orElse(null); if (role == null){ return new ArrayList<>(); } return role.getPermissions(); } /** * 获取权限列表 * @param space * @param loginUser * @return */ public List<String> getPermissionList(Space space, User loginUser) { if (loginUser == null) { return new ArrayList<>(); } // 管理员权限 List<String> ADMIN_PERMISSIONS = getPermissionsByRole(SpaceRoleEnum.ADMIN.getValue()); // 公共图库 if (space == null) { if (userService.isAdmin(loginUser)) { return ADMIN_PERMISSIONS; } return new ArrayList<>(); } SpaceTypeEnum spaceTypeEnum = SpaceTypeEnum.getEnumByValue(space.getSpaceType()); if (spaceTypeEnum == null) { return new ArrayList<>(); } // 根据空间获取对应的权限 switch (spaceTypeEnum) { case PRIVATE: // 私有空间,仅本人或管理员有所有权限 if (space.getUserId().equals(loginUser.getId()) || userService.isAdmin(loginUser)) { return ADMIN_PERMISSIONS; } else { return new ArrayList<>(); } case TEAM: // 团队空间,查询 SpaceUser 并获取角色和权限 SpaceUser spaceUser = spaceUserService.lambdaQuery() .eq(SpaceUser::getSpaceId, space.getId()) .eq(SpaceUser::getUserId, loginUser.getId()) .one(); if (spaceUser == null) { return new ArrayList<>(); } else { return getPermissionsByRole(spaceUser.getSpaceRole()); } } return new ArrayList<>(); } }

结合sa-token

Sa-Token作为国内主流的鉴权框架,是比较好学习的,由于sa-token默认保存在内存中,所以还要导入sa-token整合redis的包:

xml
复制代码
<!-- Sa-Token 权限认证 --> <dependency> <groupId>cn.dev33</groupId> <artifactId>sa-token-spring-boot-starter</artifactId> <version>1.39.0</version> </dependency> <!-- Sa-Token 整合 Redis (使用 jackson 序列化方式) --> <dependency> <groupId>cn.dev33</groupId> <artifactId>sa-token-redis-jackson</artifactId> <version>1.39.0</version> </dependency> <!-- 提供Redis连接池 --> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-pool2</artifactId> </dependency>

回到项目中,项目现在出现了两种鉴权体系,一种是针对用户是否为管理员的权限校验,另一种为用户在团队空间中的角色权限,由于之前对user的权限校验采用了注解+aop的方式实现,就不去修改对user的校验,只对团队空间进行sa-token管理。对于项目中这种有多账号认证体系的情况,采用sa-token提供的kit模式来实现多账号认证,在auth中新建StpKit.java:

java
复制代码
/** * StpLogic 门面类,管理项目中所有的 StpLogic 账号体系 * 添加 @Component 注解的目的是确保静态属性 DEFAULT 和 SPACE 被初始化 */ @Component public class StpKit { public static final String SPACE_TYPE = "space"; /** * 默认原生会话对象,项目中目前没使用到 */ public static final StpLogic DEFAULT = StpUtil.stpLogic; /** * Space 会话对象,管理 Space 表所有账号的登录、权限认证 */ public static final StpLogic SPACE = new StpLogic(SPACE_TYPE); }

通过这个门面类可以管理账号体系,在需要的地方通过StpKit.SPACE.login()来进行调用,在用户登录后,也需要把登录态保存到sa-token中:

java
复制代码
//记录用户登录态到sa-token,便于空间鉴权使用 注意保证该用户信息与SpringSession中的信息过期时间一致 StpKit.SPACE.login(user.getId()); StpKit.SPACE.getSession().set(UserConstant.USER_LOGIN_STATE,user);

权限验证

Sa-token的核心是要编写权限认证类,需要我们实现StpInterface接口中的getPermissionListgetRoleList,本项目中采用具体的权限校验,所以getRoleList方法在这里直接返回空列表即可,重点关注前者的实现,现在需求是要通过登录用户的id来获取他的角色和权限,当要判断用户是否有某个角色或者权限时sa-token就会先执行这里的方法。 getPermissionList方法的入参是登录用户id-loginId和账号体系-loginType,而目前只有一个登录Id,而我们在对图片和空间成员操作进行校验的时候也需要获取到空间id来判断用户的权限,所以需要考虑如何在sa-token中获取到我们需要的请求参数,这里我们可以使用sa-token的注解鉴权,当我们打上注解,接口被调用的时候就会立刻触发sa-token的权限校验,参数就只能通过servlet的请求对象来传递,考虑到我们的请求各有不同,传递的参数也有不同,所以需要编写一个上下文类来接收这些参数:

java
复制代码
/** * SpaceUserAuthContext * 表示用户在特定空间内的授权上下文,包括关联的图片、空间和用户信息。 */ @Data public class SpaceUserAuthContext { /** * 临时参数,不同请求对应的 id 可能不同 */ private Long id; /** * 图片 ID */ private Long pictureId; /** * 空间 ID */ private Long spaceId; /** * 空间用户 ID */ private Long spaceUserId; /** * 图片信息 */ private Picture picture; /** * 空间信息 */ private Space space; /** * 空间用户信息 */ private SpaceUser spaceUser; }

我们后端请求的地址格式都是/api/sapce/xxx这样的,只需要获取api后面第一个地址就可以知道要传递什么字段给上下文了,在StpInterfaceImpl中编写从请求中获取上下文对象的方法,这个方法的思路是先得到请求以及该请求的content-type是什么,通过content-type的值来判断是get操作还是post操作,当值为JSON的时候就是post操作,通过hutool工具类的ServletUtil的getBody方法来获取对象后赋值到上下文中,get方法则用getParamMap方法获取。获取到上下文之后我们就对url进行拆解,将地址中的/api/设为空字符串变成space/xxx,截取第一个/前面的就能知道是传的是什么id了:

java
复制代码
//默认是/api @Value("${server.servlet.context-path}") private String contextPath; /** * 从请求中获取上下文对象 * @return */ private SpaceUserAuthContext getAuthContextByRequest() { HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest(); String contentType = request.getHeader(Header.CONTENT_TYPE.getValue()); SpaceUserAuthContext authRequest; //获取请求参数 if (ContentType.JSON.getValue().equals(contentType)) { String body = ServletUtil.getBody(request); authRequest = JSONUtil.toBean(body, SpaceUserAuthContext.class); } else { Map<String, String> paramMap = ServletUtil.getParamMap(request); authRequest = BeanUtil.toBean(paramMap, SpaceUserAuthContext.class); } //根据请求路径区分id字段含义 Long id = authRequest.getId(); if (ObjUtil.isNotNull(id)){ //获取到请求路径的业务前缀 /api/picture/xxx String requestURI = request.getRequestURI(); //先替换掉上下文,剩下的就是前缀 String partURI = requestURI.replace(contextPath + "/", ""); //获取前缀的第一个斜杠前的字符串 String moduleName = StrUtil.subBefore(partURI, "/", false); switch (moduleName){ case "picture": authRequest.setPictureId(id); break; case "space": authRequest.setSpaceId(id); break; case "spaceUser": authRequest.setSpaceUserId(id); break; default: } } return authRequest; }

但是HttpServletRequest中的body值是一个流来的,只能够读取一次,而我们代码中还有其他地方需要读取到这个流,所以需要写一些包装类和过滤器来使得这个流可以重复读取,在config包下新建RequestWrapperHttpRequestWrapperFilter

java
复制代码
/** * 包装请求,使 InputStream 可以重复读取 * * @author pine */ @Slf4j public class RequestWrapper extends HttpServletRequestWrapper { private final String body; public RequestWrapper(HttpServletRequest request) { super(request); StringBuilder stringBuilder = new StringBuilder(); try (InputStream inputStream = request.getInputStream(); BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream))) { char[] charBuffer = new char[128]; int bytesRead = -1; while ((bytesRead = bufferedReader.read(charBuffer)) > 0) { stringBuilder.append(charBuffer, 0, bytesRead); } } catch (IOException ignored) { } body = stringBuilder.toString(); } @Override public ServletInputStream getInputStream() throws IOException { final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(body.getBytes()); return new ServletInputStream() { @Override public boolean isFinished() { return false; } @Override public boolean isReady() { return false; } @Override public void setReadListener(ReadListener readListener) { } @Override public int read() throws IOException { return byteArrayInputStream.read(); } }; } @Override public BufferedReader getReader() throws IOException { return new BufferedReader(new InputStreamReader(this.getInputStream())); } public String getBody() { return this.body; } }
java
复制代码
/** * 请求包装过滤器 * * @author pine */ @Order(1) @Component public class HttpRequestWrapperFilter implements Filter { @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws ServletException, IOException { if (request instanceof HttpServletRequest) { HttpServletRequest servletRequest = (HttpServletRequest) request; String contentType = servletRequest.getHeader(Header.CONTENT_TYPE.getValue()); if (ContentType.JSON.getValue().equals(contentType)) { // 可以再细粒度一些,只有需要进行空间权限校验的接口才需要包一层 chain.doFilter(new RequestWrapper(servletRequest), response); } else { chain.doFilter(request, response); } } } }

解决了获取请求参数的问题,还有一个问题就是怎么定义一套校验逻辑来同时兼容公共图库、私有空间和团队空间的权限校验,梳理下思路,我们首先肯定要判断是不是我们要的账号体系,如果不是的话就返回空权限列表,然后再看上下文中的字段,如果字段全为null则代表查的是公共空间,那么就直接返回管理员权限即可,不是的话就要看当前用户是否已经登录,用户已经登录的话,通过在上下文中先获取SpaceUser对象来根据其角色返回权限,没有SpaceUser对象或者SpaceUserId的话,就要通过spaceId或者pictureId来,如果两者都为空,那就默认管理员权限,不然的话就得到具体的空间来根据类型判断权限,对于公共空间,只有图片是用户本人上传或者用户是管理员才有管理员权限,不然只能查看图片,转为代码的话如下:

java
复制代码
public List<String> getPermissionList(Object loginId, String loginType) { // 判断 loginType,仅对类型为 "space" 进行权限校验 if (!StpKit.SPACE_TYPE.equals(loginType)) { return new ArrayList<>(); } // 管理员权限,表示权限校验通过 List<String> ADMIN_PERMISSIONS = spaceUserAuthManager.getPermissionsByRole(SpaceRoleEnum.ADMIN.getValue()); // 获取上下文对象 SpaceUserAuthContext authContext = getAuthContextByRequest(); // 如果所有字段都为空,表示查询公共图库,可以通过 if (isAllFieldsNull(authContext)) { return ADMIN_PERMISSIONS; } // 获取 userId User loginUser = (User) StpKit.SPACE.getSessionByLoginId(loginId).get(USER_LOGIN_STATE); if (loginUser == null) { throw new BusinessException(ErrorCode.NO_AUTH_ERROR, "用户未登录"); } Long userId = loginUser.getId(); // 优先从上下文中获取 SpaceUser 对象 SpaceUser spaceUser = authContext.getSpaceUser(); if (spaceUser != null) { return spaceUserAuthManager.getPermissionsByRole(spaceUser.getSpaceRole()); } // 如果有 spaceUserId,必然是团队空间,通过数据库查询 SpaceUser 对象 Long spaceUserId = authContext.getSpaceUserId(); if (spaceUserId != null) { spaceUser = spaceUserService.getById(spaceUserId); if (spaceUser == null) { throw new BusinessException(ErrorCode.NOT_FOUND_ERROR, "未找到空间用户信息"); } // 取出当前登录用户对应的 spaceUser SpaceUser loginSpaceUser = spaceUserService.lambdaQuery() .eq(SpaceUser::getSpaceId, spaceUser.getSpaceId()) .eq(SpaceUser::getUserId, userId) .one(); if (loginSpaceUser == null) { return new ArrayList<>(); } // 这里会导致管理员在私有空间没有权限,可以再查一次库处理 return spaceUserAuthManager.getPermissionsByRole(loginSpaceUser.getSpaceRole()); } // 如果没有 spaceUserId,尝试通过 spaceId 或 pictureId 获取 Space 对象并处理 Long spaceId = authContext.getSpaceId(); if (spaceId == null) { // 如果没有 spaceId,通过 pictureId 获取 Picture 对象和 Space 对象 Long pictureId = authContext.getPictureId(); // 图片 id 也没有,则默认通过权限校验 if (pictureId == null) { return ADMIN_PERMISSIONS; } Picture picture = pictureService.lambdaQuery() .eq(Picture::getId, pictureId) .select(Picture::getId, Picture::getSpaceId, Picture::getUserId) .one(); if (picture == null) { throw new BusinessException(ErrorCode.NOT_FOUND_ERROR, "未找到图片信息"); } spaceId = picture.getSpaceId(); // 公共图库,仅本人或管理员可操作 if (spaceId == null) { if (picture.getUserId().equals(userId) || userService.isAdmin(loginUser)) { return ADMIN_PERMISSIONS; } else { // 不是自己的图片,仅可查看 return Collections.singletonList(SpaceUserPermissionConstant.PICTURE_VIEW); } } } // 获取 Space 对象 Space space = spaceService.getById(spaceId); if (space == null) { throw new BusinessException(ErrorCode.NOT_FOUND_ERROR, "未找到空间信息"); } // 根据 Space 类型判断权限 if (space.getSpaceType() == SpaceTypeEnum.PRIVATE.getValue()) { // 私有空间,仅本人或管理员有权限 if (space.getUserId().equals(userId) || userService.isAdmin(loginUser)) { return ADMIN_PERMISSIONS; } else { return new ArrayList<>(); } } else { // 团队空间,查询 SpaceUser 并获取角色和权限 spaceUser = spaceUserService.lambdaQuery() .eq(SpaceUser::getSpaceId, spaceId) .eq(SpaceUser::getUserId, userId) .one(); if (spaceUser == null) { return new ArrayList<>(); } return spaceUserAuthManager.getPermissionsByRole(spaceUser.getSpaceRole()); } }

注解应用

现在权限列表有了,就到了写权限校验注解了,但使用注解要自己指定账号体系的话会很麻烦,而sa-token提供了注解合并来简化代码,根据文档我们需要新建一个配置类和注解,在auth/annotation下创建配置类:

java
复制代码
@Configuration public class SaTokenConfigure implements WebMvcConfigurer { // 注册 Sa-Token 拦截器,打开注解式鉴权功能 @Override public void addInterceptors(InterceptorRegistry registry) { // 注册 Sa-Token 拦截器,打开注解式鉴权功能 registry.addInterceptor(new SaInterceptor()).addPathPatterns("/**"); } @PostConstruct public void rewriteSaStrategy() { // 重写Sa-Token的注解处理器,增加注解合并功能 SaAnnotationStrategy.instance.getAnnotation = (element, annotationClass) -> { return AnnotatedElementUtils.getMergedAnnotation(element, annotationClass); }; } }

创建空间账号体系的鉴权注解:

java
复制代码
/** * 空间权限认证:必须具有指定权限才能进入该方法 * <p> 可标注在函数、类上(效果等同于标注在此类的所有方法上) */ @SaCheckPermission(type = StpKit.SPACE_TYPE) @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD, ElementType.TYPE}) public @interface SaSpaceCheckPermission { /** * 需要校验的权限码 * * @return 需要校验的权限码 */ @AliasFor(annotation = SaCheckPermission.class) String[] value() default {}; /** * 验证模式:AND | OR,默认AND * * @return 验证模式 */ @AliasFor(annotation = SaCheckPermission.class) SaMode mode() default SaMode.AND; /** * 在权限校验不通过时的次要选择,两者只要其一校验成功即可通过校验 * * @return / */ @AliasFor(annotation = SaCheckPermission.class) String[] orRole() default {}; }

在对应的controller接口上加上注解@SaSpaceCheckPermission(value = SpaceUserPermissionConstant.PICTURE_EDIT)就可以实现鉴权了,同时记得在返回给前端的SpaceVo和PictureVo中增加权限列表字段方便前端鉴权。

ShardingSphere

ShardingSphere是一个分库分表框架,当一张表的数据量非常多的时候,便可以通过分表来解决性能问题。ShardingSphere提供了两个模块,JDBC和Proxy,本项目采用JDBC模块。

分表的策略有静态和动态分表,静态顾名思义就是写死了分表的规则,动态分表则能自定义结构和规则,显然动态分表适合本项目,首先在配置文件中编写相关配置:

yml
复制代码
# 空间图片分表 shardingsphere: datasource: names: 数据库名字 数据库名字: type: com.zaxxer.hikari.HikariDataSource driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/数据库名字 username: root password: 123456 rules: sharding: tables: picture: actual-data-nodes: 数据库名字.表名 # 动态分表 table-strategy: standard: sharding-column: 分键值 #在本项目是spaceId sharding-algorithm-name: 表名_sharding_algorithm # 使用自定义分片算法 sharding-algorithms: 表名_sharding_algorithm: type: CLASS_BASED props: strategy: standard algorithmClassName: 自定义算法类所在包地址.表名ShardingAlgorithm props: sql-show: true

其中actual-data-nodes设置为逻辑表数据库名字.表名是因为我们的spaceID是长整型,不能通过数据库名字.表名_${0...9999}的方式来指定范围。 sharding-column是我们要用来分表的字段,sharding-algorithm-name是我们自定义的分片算法。algorithmClassName是我们自定义算法类的地址,这个算法类必须要实现StandardShardingAlgorithm接口,项目中我们的算法如下:

java
复制代码
public class PictureShardingAlgorithm implements StandardShardingAlgorithm<Long> { @Override public String doSharding(Collection<String> availableTargetNames, PreciseShardingValue<Long> preciseShardingValue) { //配置文件中已经定义sharding-column: spaceId Long spaceId = preciseShardingValue.getValue(); String logicTableName = preciseShardingValue.getLogicTableName(); // spaceId 为 null 表示查询所有图片 if (spaceId == null) { return logicTableName; } // 根据 spaceId 动态生成分表名 String realTableName = "picture_" + spaceId; if (availableTargetNames.contains(realTableName)) { return realTableName; } else { return logicTableName; } } @Override public Collection<String> doSharding(Collection<String> collection, RangeShardingValue<Long> rangeShardingValue) { return new ArrayList<>(); } @Override public Properties getProps() { return null; } @Override public void init(Properties properties) { } }

我们规定spaceId为空的时候就去查逻辑表,不然就去查实际分表。由于ShardingSphere的分配逻辑初始化默认获取的是actual-data-nodes的值,而我们这里写死了,所以还要自己写一个分表管理器来更新actual-data-nodes,由于代码太多这里就不贴上来了,大概来说就是这个方法里定义了获取分表列表的方法、更新配置的方法以及动态创建分表的方法,其中动态创建分表中用到了MybatisPlus的SqlRunner.db().update("sql语句"),使用这个方法需要开启配置:

yml
复制代码
mybatis-plus: global-config: enable-sql-runner: true

在创建空间的方法中调用该方法即可实现动态创建分表。

但是呢通过测试用例发现,插入图片的时候一定要指定spaceId,不然系统会报异常,除了插入,删除,更新,查询时都需要补充spaceId才能避免报错和多表查询(不指定spaceId会查多个物理表):

image.png image.png

这里记得是0L,一开始漏写L导致出了bug,

image.png image.png

此外在编辑图片的时候也发现了一个问题,就是我们上传图片和编辑图片用的都是uploadPicture方法,这个方法里面我们用了saveOrUpdate方法来让框架判断是插入还是更新,而由于spaceId是作为分键值不能进行更新,传入该方法的picture对象中spaceId字段是有值的,框架会选择对它进行更新,即使这个值没有发生变化,一开始我加了下面这么一个处理,即更新图片时设置spaceId为null:

java
复制代码
if (pictureId != null){ picture.setId(pictureId); //因为用了分库分表,且spaceId是作为分键值,不能修改分键值. 此处代码为新增代码,关闭分库分表时注释掉即可 picture.setSpaceId(null); picture.setEditTime(new Date()); }

但是分表框架又依赖于spaceId,设置为null的话可能会导致ShardingSphere无法正确路由到分片表从而出现异常,通过问AI有个解决方法就是给Picture的spaceId字段加上@TableField(updateStrategy = FieldStrategy.NEVER)注解,这样就不会对其进行更新,经过试验也确实不会出现异常了。

然而我的代码里还有一些地方没进行修改,查单张图片时前端只传了图片id没有空间id,要加多一个spaceId的话两边都需要修改,上面提到的saveOrUpdate里的where条件是id和isDelete,它还是会执行多条sql语句。对于目前的体量的话,我还是选择先关掉它吧,还是担心自己容易改出问题然后查半天,关闭的话加个注解即可: @SpringBootApplication(exclude = {ShardingSphereAutoConfiguration.class})

以上就是这期学习的记录了,即使自己写完感觉还是不太熟,还是需要多看几遍教程和代码理解才行。

0个评论
点击登录,快来和大家讨论吧~
表情
图片
暂无评论
shark
作者分享
做了一个月的小程序,终于准备到即将交付的时候了,这段时间里可真是被微信小程序的各种限制折磨的不要不要的,突出一个为难开发者,有几次改代码改到了两点多才改完!小程序的后端技术栈和鱼皮的智能Bi项目采用了差不多的技术栈,通过注解+aop来对接口进行令牌桶限流,用rabbitmq来缓解调用Ai服务的压力,异步生成报告,对于订单采用了自定义线程池结合async+scheduled注解实现定时任务的异步执行,每分钟遍历删除库中的订单,还有些功能等真正交付后再做下总结。唉,不知道这个项目再加上智能图库项目能不能让我这种0相关工作经验的双非往届生有面试机会和找到工作呢,不想再次尝试付出与回报不成正比的滋味了😭😭
8
今天收到了甲方的第一笔付款💰,没想到毕业一年半居然能用自己专业技能接个活赚了点外快,还是有点小高兴的。 这两天的学习状态不太行,我好像总会在经历了持续一段时间的学习后会有两三天学不进的状态🤧,不知道大伙会不会有这种状态,虽然我很想克服这种状态,但心里就好像有东西堵在那影响着学不进去(不知道是不是焦虑),希望明天能恢复状态吧。 转眼到了3月份,接的活的时间也剩下半个月了,下半部分就要开发调用Ai接口的代码和微信支付(感觉好像挺麻烦?)的代码了,同时也需要开始准备看面试题了,想问下各位是怎么使用面试鸭来高效刷题的呢,这两天(可能跟我的状态有关)看面试题总是有种似记住但又没全记住的感觉,上次背八股还是22年秋招的时候,对于一些知识可能还有记得一些,但由于我是0开发经验社招,不知道重点要看哪些好,希望各位大佬能给小弟一点建议🙏
4
这几天没有打卡,不是摆烂了,而是前几天有个朋友推了个活给我问我说做不做,是做一个微信小程序,并且需要调用Ai的接口,他因为工作太忙没有空接这个活,而我本来正在学习Bi项目,打算学完之后写简历刷面试题投简历,所以一开始还在犹豫,觉得自己23届毕业,虽说大学自学了几年Java,但是0相关工作经验,有点没底气,不过后来我拉到了另一个同学来帮忙,那个同学在大厂实习过,有过企业开发的经验,他也乐意加入一起做项目,于是最后便接下了这个活(主要是有钱可以拿)。由于自己是前端苦手,所以也是一边问Ai一边改页面的布局,改到今天觉得差不多了,甲方也觉得ok,后面就准备和同学一起写后端的接口了,在这几天也对着项目需求跟同学讨论了许多功能和结构上的设计,也算是一种积累经验的过程了,希望这一个月的工期内能把项目做好上线,最好是能写到简历上吧,孩子真的很想要一份工作!!这一个月干的活希望不要是白忙活,不然我真的要道心破碎了😭😭😭
7
智能Bi项目-限流优化
7
智能BI项目-调用硅基流动的DeepSeek
19
下载 APP