When rewriting the task allocation algorithm, it is necessary to obtain the current logged-in user area and its sub-regions. When testing after rewriting, it is found that the task is directly stuck in the allocation, and an error is found when viewing the background log.
View where the error is thrown based on the error
It can be found that there is a problem when calling the getAuthUserDetailWithoutTransaction method. Check this method further:
public Optional<AuthUserDetails> getAuthUserDetailWithoutTransaction() {
logger.debug("根据认证获取当前登录用户名,并获取该用户");
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication != null) {
AuthUserDetails userDetail;
if (authentication instanceof UsernamePasswordAuthenticationToken) {
userDetail = (AuthUserDetails) authentication.getPrincipal();
} else if (authentication instanceof AuthUserDetails) {
userDetail = (AuthUserDetails) authentication;
} else if (authentication instanceof AnonymousAuthenticationToken) {
return Optional.empty();
} else {
throw new RuntimeException("获取类型不正确");
}
return Optional.of(userDetail);
}
logger.debug("认证用户在数据库中不存在");
return Optional.empty();
}
According to the output of the background log 认证用户在数据库中不存在
, it can be concluded that authentication
is null, and then for further confirmation, I tried to run the background in debug mode and found that the above function is often used during the background operation. , and the obtained authentication
are not null
Only returned when assigning tasks authentication
is null
The function call relationship can be known from the following simplified diagram:
And because the error message contains
Unexpected exception occurred invoking async method(调用异步方法时发生意外异常)
So I tried to remove the asynchronous annotation of the addTaskDetailsAndAddFormItemValuesAndUpdateStatistics
method. After re-executing, I found that everything was normal and no error was reported.
So I went to inquire about SecurityContextHolder.getContext()
and @Async
at the same time the problem that will occur, and found that the @Async method uses SecurityContextHolder.getContext()
and it will return
null.
test:
operation result:
That is, calling in an asynchronous function will return null, and we can also find that our current default thread is nio-8081-exec-8, but the asynchronous function will create a new task-1 thread, and the security cannot be obtained in the new thread Context, it can be speculated that by default the security context will only exist in the main thread, it will not be passed along with the new thread.
SecurityContextHolder is used to store information about the security context. Who is the user currently operating, whether the user has been authenticated, what role permissions he has... These are all stored in the SecurityContextHolder. SecurityContextHolder uses the ThreadLocal strategy by default to store authentication information. This also means that this is a thread-bound strategy. Spring Security automatically binds the authentication information to the current thread when the user logs in, and automatically clears the authentication information of the current thread when the user logs out.
If we set the security context policy like this in getAuthUserDetailWithoutTransaction
it can solve this problem, but this may affect other methods that call this method, so we have to find a new solution.
public Optional<AuthUserDetails> getAuthUserDetailWithoutTransaction() {
logger.debug("根据认证获取当前登录用户名,并获取该用户");
//设置可继承本地线程策略,使得其可以在异步方法中被调用
SecurityContextHolder.setStrategyName(SecurityContextHolder.MODE_INHERITABLETHREADLOCAL);
. . .
}
At first I thought that I only need to set the strategy in this asynchronous method, but after testing, I found that this strategy will not be passed when the method is called.
So I tried to get the current logged-in user directly at the M layer and then passed the logged-in user to the asynchronous method
But when I try again, a new error occurs
failed to lazily initialize a collection of role: club.yunzhi.smartcommunity.entity.District.children, could not initialize proxy - no Session
After searching, it is found that Hibernate usually uses lazy loading when performing association processing, which can avoid multiple transfers of a large amount of data. That is to say, we have obtained the user in Aservice, and the sub-area corresponding to the user can be called in Aservice, but if the user is If it is passed to Bservice again, if you want to call the sub-area of the area corresponding to the user, an error will occur.
Source service:
Accept the delivered service:
That is to say, in order to avoid this situation when passing parameters, we can directly pass the corresponding id, and then obtain our data through the warehouse layer according to the ID.
That is to say, we can change it to the following way to achieve the goal
public void taskAssign(){
. . .
Optional<Long> optionalWebUserId = this.webUserService.getCurrentLoginWebUserId();
Long webUserId = optionalWebUserId.orElse(null);
// 异步生成任务详情与表单,并更新各区域统计情况。
this.taskDetailAsyncService.addTaskDetailsAndAddFormItemValuesAndUpdateStatistics(task, residents, specification, webUserId);
}
List<District> getManageDistrictsWithCurrentLoginUser(Long ...webUserId);
public final List<District> getManageDistrictsWithCurrentLoginUser(Long ...webUserId) {
List<District> result;
Optional<WebUser> optionalUser;
if(webUserId.length == 0) {
optionalUser = this.webUserService.getCurrentLoginWebUser();
} else {
optionalUser = this.webUserRepository.findById(webUserId[0]);
}
. . .
}
At this point try again to achieve the effect we want.
In addition, let's talk about how to display multiple selection or single selection components, the effect we want to achieve:
And the user clicking option 2 will not change the display, which is similar to the effect of disable, but if we directly declare the input as disable, it will not be displayed very clearly as shown below.
If we often use redonly, we will find that the corresponding display will still be given after clicking, but the value will not be changed.
Solution: declare onclick as return false in input
<input
. . .
onclick = "return false"
type="radio" >
Question three:
If a person has multiple properties in the project, after assigning the task and then previewing it, multiple records will be found
When I encounter this situation, the first thing that comes to my mind is that the data generated is duplicated when assigning tasks, but I did not find any problems after reading the assignment code, and no duplicate data items were generated in the database. It also confirmed that the port called before matches the background, so I tried to output the page in the foreground, and found that the returned data was exactly the same. I also checked the data returned to the foreground in the background and found that three identical data were returned. The corresponding id is also the same.
At this point, it can be confirmed that there is a problem with the query.
However, we did not pass any query parameters in the foreground, so it can basically be confirmed that there is a problem in the background when querying according to the user area.
The background query condition is constructed like this
Join<TaskDetail, Building> buildingJoin = root.join("resident")
.join("houses", JoinType.LEFT)
.join("building", JoinType.LEFT);
switch (district.getType()) {
case TYPE_BUILDING:
return criteriaBuilder.equal(buildingJoin.get("id").as(Long.class), districtId);
. . .
}
That is to say, each query is independent. Well, after the house1 query returns a piece of data, and then returns it according to other house queries, it is not the way I imagined the following picture:
If we want to remove these duplicates, we only need to add this one to the query condition:
public static Specification<TaskDetail> distinct() {
return (root, criteriaQuery, criteriaBuilder) -> {
criteriaQuery.distinct(true);
return criteriaQuery.getRestriction();
};
}
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。