Merge remote-tracking branch 'origin/main'

# Conflicts:
#	collect_information/types/subPages/projectManager/myProject/index.ts
This commit is contained in:
wangyilin 2024-11-20 10:27:16 +08:00
commit 458d442dd9
39 changed files with 915 additions and 113 deletions

View File

@ -1,7 +1,7 @@
# 配置文档参考 https://taro-docs.jd.com/docs/next/env-mode-config
TARO_APP_ID="wx0acd1c4fcf94bdd3"
TARO_APP_BASE_API="http://172.10.10.93:8765"
# TARO_APP_BASE_API="https://www.hnjinglian.cn:5678"
# TARO_APP_BASE_API="http://172.10.10.93:8765"
TARO_APP_BASE_API="https://www.hnjinglian.cn:5678"

View File

@ -3,7 +3,7 @@
<view v-if="number !== 0" class="myProjectItem" v-for="(item, index) in myProjectList" :key="index">
<view style="display: flex; justify-content: space-between">
<text>{{ item?.name }}</text>
<text>单位类型{{ item?.type.label }}</text>
<text>单位类型{{ item?.type?.label }}</text>
</view>
<view class="myProjectIndex"
>地址
@ -18,7 +18,7 @@
<view style="border: 1px solid #cccccc; color: #9b9b9f">
<view style="display: flex; justify-content: space-between">
<text>项目名称{{ serviceProject.name }}</text>
<text>项目类型{{serviceProject.type.label}}</text>
<text>项目类型{{ serviceProject.type?.label }}</text>
</view>
<view style="display: flex; justify-content: space-between">
<text>项目负责人{{ serviceProject.projectManagerMiniProgramUserInfo?.name ? serviceProject.projectManagerMiniProgramUserInfo?.name : '无分配项目经理' }}</text>
@ -26,7 +26,6 @@
<view>责任单位{{ serviceProject.securityUnitName }}</view>
</view>
</view>
</view>
</view>
<view v-else class="myProject">

View File

@ -7,9 +7,7 @@
<!-- <text>进行中</text>-->
</view>
<view>
<view style="float: left;width: 50%;" class="content">
经理名称:{{ serviceProject?.projectManagerMiniProgramUserInfo.name }}
</view>
<view style="float: left; width: 50%" class="content"> 经理名称:{{ serviceProject?.projectManagerMiniProgramUserInfo.name }} </view>
<view class="content">手机号:{{ serviceProject?.projectManagerMiniProgramUserInfo.telephone }}</view>
</view>
</view>
@ -47,9 +45,16 @@
<!--表格-->
<view class="projectDetailsTableDrop">
<view style="padding: 0 12px">项目人员</view>
<scroll-view :scroll-y="true" style="height: 80%;" @scrolltoupper="upper" @scrolltolower="lower"
:scroll-into-view="toView" :scroll-top="scrollTop" :refresherEnabled="true"
@refresherrefresh="onRefresherRefresh" :refresher-triggered="isRefresher"
<scroll-view
:scroll-y="true"
style="height: 80%"
@scrolltoupper="upper"
@scrolltolower="lower"
:scroll-into-view="toView"
:scroll-top="scrollTop"
:refresherEnabled="true"
@refresherrefresh="onRefresherRefresh"
:refresher-triggered="isRefresher"
>
<view class="projectDetailsTable" v-for="(item, index) in securityUserList" :key="index">
<view>
@ -66,7 +71,7 @@
</view>
<view style="display: flex; justify-content: space-between">
<text>创建时间:{{ item.createTime }}</text>
<text>身份证:{{ item.idCard }}</text>
<text>身份证:{{ item.idCard?.desensitizedValue }}</text>
</view>
</view>
</view>
@ -74,16 +79,15 @@
</view>
</scroll-view>
</view>
</view>
</template>
<script setup lang="ts">
import Taro, {useLoad} from "@tarojs/taro";
import Taro, { useLoad } from '@tarojs/taro'
import './projectDetails.scss'
import {ref} from "vue";
import api from "@/request/index";
import { ref } from 'vue'
import api from '@/request/index'
import dayjs from 'dayjs'
import {ServiceProjectSecurityUserPagerVo} from "@/types/subPages/projectManager/myProject";
import { ServiceProjectSecurityUserPagerVo } from '@/types/subPages/projectManager/myProject'
const serviceProject = ref()
const enterprisesUnitName = ref('')
@ -91,7 +95,7 @@ const securityUserList = ref<ServiceProjectSecurityUserPagerVo[]>([])
useLoad(async (options) => {
enterprisesUnitName.value = options.enterprisesUnitName
serviceProject.value = JSON.parse(options.serviceProject)
console.log(serviceProject.value);
console.log(serviceProject.value)
await projectDetailsTable()
})
const projectDetailsTable = async () => {
@ -104,8 +108,8 @@ const projectDetailsTable = async () => {
},
page: {
size: 4,
current: current.value
}
current: current.value,
},
}
const resp = await api.post<PagerVo<ServiceProjectSecurityUserPagerVo>>('/miniProgramUser/securityUserPager', queryParams)
securityUserList.value = [...securityUserList.value, ...resp.data!.records]

View File

@ -12,11 +12,13 @@ VITE_APP_PROXY_URL=http://172.10.10.93:8765
# rsa 公钥
VITE_APP_RSA_PUBLIC_KEY=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDJps/EXxxSpEM1Ix4R0NWIOBciHCr7P7coDT8tNKfelgR7txcJOqHCO/MIWe7T04aHQTcpQxqx9hMca7dbqz8TZpz9jvLzE/6ZonVKxHsoFnNlHMp1/CPAJ9f6D9wYicum2KltJkmQ0g//D9W2zPCYoGOmSRFcZx/KEBa4EM53jQIDAQAB
# 高德
VITE_APP_GAODE_KEY=ca549d915cb38803582ca7e85c5f972c
VITE_APP_GAODE_VERSION=2.0
VITE_APP_SECURITY_JS_CODE=f464462874676b3f1469780a62e5b921
# VITE_APP_GAODE_KEY=f379a3f860a68d7438526275d6a94b05
# 高德 myx
# VITE_APP_GAODE_KEY=ca549d915cb38803582ca7e85c5f972c
# VITE_APP_GAODE_VERSION=2.0
# VITE_APP_SECURITY_JS_CODE=432125a0f8d8cad2dac38b77d6f6728f
# VITE_APP_SECURITY_JS_CODE=f464462874676b3f1469780a62e5b921
# 高德 lz
VITE_APP_GAODE_KEY=f379a3f860a68d7438526275d6a94b05
VITE_APP_GAODE_VERSION=2.0
VITE_APP_SECURITY_JS_CODE=432125a0f8d8cad2dac38b77d6f6728f

View File

@ -10,10 +10,12 @@ declare module 'vue' {
AAvatar: typeof import('ant-design-vue/es')['Avatar']
AButton: typeof import('ant-design-vue/es')['Button']
ACascader: typeof import('ant-design-vue/es')['Cascader']
ACheckbox: typeof import('ant-design-vue/es')['Checkbox']
ACheckboxGroup: typeof import('ant-design-vue/es')['CheckboxGroup']
ACol: typeof import('ant-design-vue/es')['Col']
AConfigProvider: typeof import('ant-design-vue/es')['ConfigProvider']
ADatePicker: typeof import('ant-design-vue/es')['DatePicker']
ADivider: typeof import('ant-design-vue/es')['Divider']
AdministrativeDivisionTree: typeof import('./src/components/tree/AdministrativeDivisionTree.vue')['default']
ADropdown: typeof import('ant-design-vue/es')['Dropdown']
AForm: typeof import('ant-design-vue/es')['Form']
@ -30,6 +32,7 @@ declare module 'vue' {
AMenuItem: typeof import('ant-design-vue/es')['MenuItem']
AModal: typeof import('ant-design-vue/es')['Modal']
APagination: typeof import('ant-design-vue/es')['Pagination']
APopconfirm: typeof import('ant-design-vue/es')['Popconfirm']
APopover: typeof import('ant-design-vue/es')['Popover']
ARadioGroup: typeof import('ant-design-vue/es')['RadioGroup']
ARangePicker: typeof import('ant-design-vue/es')['RangePicker']
@ -39,6 +42,8 @@ declare module 'vue' {
ASpin: typeof import('ant-design-vue/es')['Spin']
ASubMenu: typeof import('ant-design-vue/es')['SubMenu']
ATable: typeof import('ant-design-vue/es')['Table']
ATabPane: typeof import('ant-design-vue/es')['TabPane']
ATabs: typeof import('ant-design-vue/es')['Tabs']
ATag: typeof import('ant-design-vue/es')['Tag']
ATextarea: typeof import('ant-design-vue/es')['Textarea']
ATimePicker: typeof import('ant-design-vue/es')['TimePicker']

View File

@ -24,7 +24,7 @@ const saveOrUpdateEnterprisesUnit = (params: _FormType, callback: Function) => {
const _mapRef = ref<ComponentExposed<typeof MapContainer>>(null)
const _formParams = ref<_FormType>({ ...params })
let city = '全国'
let city = ''
const initMarker = (map: AMap.Map) => {
//添加maker点 设置point
const maker = new AMap.Marker({

View File

@ -3,3 +3,4 @@ target
logs
temp
HELP.md
rebel.xml

View File

@ -15,5 +15,17 @@ import java.lang.annotation.Target;
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Desensitized {
/**
* 脱敏类型
*
* @return 脱敏类型
*/
DesensitizedUtil.DesensitizedType value();
/**
* 是否保留原字段
*
* @return 原字段+加密字段 的map
*/
boolean keepOriginalField() default false;
}

View File

@ -1,14 +0,0 @@
package com.changhu.common.annotation;
import java.lang.annotation.*;
/**
* @author 20252
* @createTime 2024/6/26 上午9:41
* @desc RealDelete...
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RealDelete {
}

View File

@ -35,19 +35,19 @@ public class GlobalCacheManager {
}
static void initEnum() {
log.info("初始化枚举字典数据----");
//在包下扫描出BaseEnum的子类
Set<Class<?>> classes = ClassUtil.scanPackageBySuper("com.changhu.common.db.enums", BaseEnum.class);
//序列化方法
Method superSerializer = ClassUtil.getDeclaredMethod(BaseEnum.class, "serializer");
for (Class<?> aClass : classes) {
Method childrenSerializer = ClassUtil.getDeclaredMethod(aClass, "serializer");
BaseEnum<?>[] enumConstants = (BaseEnum<?>[]) aClass.getEnumConstants();
if (enumConstants == null) {
continue;
}
// 获取所有字段
Field[] fields = aClass.getDeclaredFields();
// 过滤出非静态字段并带有 IsExtData 注解的字段
List<Field> extDataFields = Arrays.stream(fields)
//子类是否重写了父类的serializer方法
boolean isRewriteSerializer = !superSerializer.equals(ClassUtil.getDeclaredMethod(aClass, "serializer"));
// 获取所有字段并过滤出非静态字段并带有 IsExtData 注解的字段
List<Field> extDataFields = Arrays.stream(aClass.getDeclaredFields())
.filter(field -> !Modifier.isStatic(field.getModifiers()))
.filter(field -> field.getAnnotation(IsExtData.class) != null)
.collect(Collectors.toList());
@ -56,7 +56,7 @@ public class GlobalCacheManager {
Stream.of(enumConstants).forEach(v -> {
//如果子类重写父类的方法 则直接使用子类的结果
if (!superSerializer.equals(childrenSerializer)) {
if (isRewriteSerializer) {
map.put(v, v.serializer());
} else {
SelectNodeVo<Object> vo = new SelectNodeVo<>();

View File

@ -0,0 +1,28 @@
package com.changhu.common.db.enums;
import com.baomidou.mybatisplus.annotation.IEnum;
import com.changhu.common.annotation.IsExtData;
import com.changhu.common.db.BaseEnum;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* @author 20252
* @createTime 2024/9/5 上午11:04
* @desc 服务项目二级类型
*/
@AllArgsConstructor
@Getter
public enum ServiceProjectTwoType implements BaseEnum<String>, IEnum<String> {
SECURITY("outsource", "外包", ServiceProjectType.SECURITY.getValue()),
PROPERTY_RECRUITMENT("property_recruitment", "物业自招", ServiceProjectType.SECURITY.getValue()),
OTHER_RECRUITMENT("other_recruitment", "其他自招", ServiceProjectType.SECURITY.getValue()),
;
private final String value;
private final String label;
@IsExtData
private final String parentType;
}

View File

@ -15,7 +15,6 @@ import lombok.Getter;
public enum ServiceProjectType implements BaseEnum<String>, IEnum<String> {
SECURITY("security", "安保"),
PROPERTY("property", "物业"),
;

View File

@ -0,0 +1,26 @@
package com.changhu.common.pojo.vo;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* @author 20252
* @createTime 2024/11/19 上午9:17
* @desc 脱敏数据
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class DesensitizedVo {
/**
* 原始数据
*/
private String originalValue;
/**
* 脱敏数据
*/
private String desensitizedValue;
}

View File

@ -0,0 +1,89 @@
package com.changhu.controller;
import cn.hutool.core.annotation.AnnotationUtil;
import cn.hutool.core.lang.Dict;
import com.alibaba.fastjson2.JSON;
import com.changhu.common.annotation.CheckUserType;
import com.changhu.common.annotation.JsonBody;
import com.changhu.common.db.enums.UserType;
import com.changhu.common.pojo.vo.SelectNodeVo;
import com.changhu.pojo.entity.AccessKeys;
import com.changhu.pojo.params.GeneratedAccessKeyParams;
import com.changhu.service.AccessKeysService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import java.util.Arrays;
import java.util.List;
/**
* @author 20252
* @createTime 2024/11/19 上午10:38
* @desc AccessKeysController...
*/
@Tag(name = "开放平台")
@RequestMapping("/accessKeys")
@JsonBody
@CheckUserType(userTypes = UserType.MANAGEMENT_SUPER)
public class AccessKeysController {
@Autowired
private AccessKeysService accessKeysService;
@GetMapping("/listOpenApi")
public List<SelectNodeVo<String>> listOpenApi() {
Class<OpenController> openControllerClass = OpenController.class;
RequestMapping requestMapping = AnnotatedElementUtils.findMergedAnnotation(openControllerClass, RequestMapping.class);
String controllerPath = requestMapping.value()[0];
return Arrays.stream(openControllerClass.getMethods())
.filter(m -> AnnotationUtil.hasAnnotation(m, RequestMapping.class))
.map(m -> {
Operation operation = m.getAnnotation(Operation.class);
RequestMapping mReq = AnnotatedElementUtils.findMergedAnnotation(m, RequestMapping.class);
return SelectNodeVo.<String>builder()
.value(controllerPath + mReq.value()[0])
.label(operation.summary())
.extData(Dict.of("methods", mReq.method()))
.build();
})
.toList();
}
@Operation(summary = "生成访问凭证")
@PostMapping("/generatedAccessKey")
public void generatedAccessKey(@RequestBody GeneratedAccessKeyParams params) {
accessKeysService.generatedAccessKey(params);
}
@Operation(summary = "访问凭证列表")
@GetMapping("/tableList")
public List<AccessKeys> tableList() {
return accessKeysService.list();
}
public static void main(String[] args) {
Class<OpenController> openControllerClass = OpenController.class;
RequestMapping requestMapping = AnnotatedElementUtils.findMergedAnnotation(openControllerClass, RequestMapping.class);
String controllerPath = requestMapping.value()[0];
List<SelectNodeVo<String>> method = Arrays.stream(openControllerClass.getMethods())
.filter(m -> AnnotationUtil.hasAnnotation(m, RequestMapping.class))
.map(m -> {
Operation operation = m.getAnnotation(Operation.class);
RequestMapping mReq = AnnotatedElementUtils.findMergedAnnotation(m, RequestMapping.class);
return SelectNodeVo.<String>builder()
.value(controllerPath + mReq.value()[0])
.label(operation.summary())
.extData(Dict.of("method", mReq.method()))
.build();
})
.toList();
method.forEach(i -> System.out.println(JSON.toJSONString(i)));
}
}

View File

@ -6,6 +6,8 @@ import com.changhu.common.enums.OpenApiType;
import com.changhu.common.pojo.vo.SelectNodeVo;
import com.changhu.pojo.dto.DataViewDTO;
import com.changhu.pojo.dto.EnterprisesUnitDetailDTO;
import com.changhu.pojo.dto.SecurityUnitUseStatisticsDTO;
import com.changhu.pojo.dto.ServiceProjectSecurityUserRosterDTO;
import com.changhu.service.OpenApiService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.media.Schema;
@ -51,4 +53,19 @@ public class OpenController {
public DataViewDTO dataView() {
return openApiService.dataView();
}
@Operation(summary = "保安单位使用情况统计")
@CheckOpenApi(value = OpenApiType.Information_on_enterprises_and_institutions)
@GetMapping("/securityUnitUseStatistics")
public List<SecurityUnitUseStatisticsDTO> securityUnitUseStatistics(@Schema(description = "代码") @RequestParam String code,
@Schema(description = "等级") @RequestParam Integer level) {
return openApiService.securityUnitUseStatistics(code, level);
}
@Operation(summary = "服务项目安保人员花名册")
@CheckOpenApi(value = OpenApiType.Information_on_enterprises_and_institutions)
@GetMapping("/serviceProjectUserRoster")
public List<ServiceProjectSecurityUserRosterDTO> serviceProjectUserRoster(@Schema(description = "服务项目id") Long serviceProjectId) {
return openApiService.serviceProjectUserRoster(serviceProjectId);
}
}

View File

@ -0,0 +1,15 @@
package com.changhu.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.changhu.pojo.entity.AccessKeys;
import org.apache.ibatis.annotations.Mapper;
/**
* access_keys (开放接口的授权) 固化类
* author: luozhun
* desc 由groovy脚本自动生成
*/
@Mapper
public interface AccessKeysMapper extends BaseMapper<AccessKeys> {
}

View File

@ -1,6 +1,8 @@
package com.changhu.mapper;
import com.changhu.common.pojo.vo.SelectNodeVo;
import com.changhu.pojo.dto.SecurityUnitUseStatisticsDTO;
import com.changhu.pojo.dto.ServiceProjectSecurityUserRosterDTO;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
@ -23,4 +25,22 @@ public interface OpenApiMapper {
*/
List<SelectNodeVo<Long>> getEnterprisesUnit(@Param("code") String code,
@Param("level") Integer level);
/**
* 保安单位使用情况统计
*
* @param code 代码
* @param level 等级
* @return 结果
*/
List<SecurityUnitUseStatisticsDTO> securityUnitUseStatistics(@Param("code") String code,
@Param("level") Integer level);
/**
* 获取服务人员花名册
*
* @param serviceProjectId 服务项目id
* @return 结果
*/
List<ServiceProjectSecurityUserRosterDTO> serviceProjectUserRoster(@Param("serviceProjectId") Long serviceProjectId);
}

View File

@ -2,6 +2,7 @@ package com.changhu.module.management.pojo.entity;
import com.baomidou.mybatisplus.annotation.TableName;
import com.changhu.common.db.enums.IsOrNot;
import com.changhu.common.db.enums.ServiceProjectTwoType;
import com.changhu.common.db.enums.ServiceProjectType;
import com.changhu.support.mybatisplus.pojo.entity.BaseEntity;
import lombok.AllArgsConstructor;
@ -56,12 +57,22 @@ public class ServiceProject extends BaseEntity implements Serializable {
private ServiceProjectType type;
/**
* 是否自招保安(只有当type为property 此字段必填)
* 二级类型(外包/物业自招/其他自招)
*/
private IsOrNot isRecruitSecurity;
private ServiceProjectTwoType twoType;
/**
* 证件号(服务类型为保安必填 服务类型为物业则需自招保安为必填)
* 若二级类型选择外包 这里需填写外包公司名称
*/
private String outsourceName;
/**
* 是否备案
*/
private IsOrNot isFiling;
/**
* 证件号(保安服务许可证/备案证)
*/
private String idNumber;

View File

@ -1,6 +1,7 @@
package com.changhu.module.management.pojo.params;
import com.changhu.common.db.enums.IsOrNot;
import com.changhu.common.db.enums.ServiceProjectTwoType;
import com.changhu.common.db.enums.ServiceProjectType;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
@ -32,10 +33,16 @@ public class ServiceProjectSaveOrUpdateParams {
@NotNull(message = "服务类型不能为空")
private ServiceProjectType type;
@Schema(description = "是否自招保安(只有当type为property 此字段必填)")
private IsOrNot isRecruitSecurity;
@Schema(description = "二级类型")
private ServiceProjectTwoType twoType;
@Schema(description = "证件号(服务类型为保安必填 服务类型为物业则需自招保安为必填)")
@Schema(description = "外包公司名称")
private String outsourceName;
@Schema(description = "是否备案")
private IsOrNot isFiling;
@Schema(description = "证件号(保安服务许可证/备案证)")
private String idNumber;
@Schema(description = "服务区域面积")

View File

@ -1,6 +1,7 @@
package com.changhu.module.management.pojo.vo;
import com.changhu.common.db.enums.IsOrNot;
import com.changhu.common.db.enums.ServiceProjectTwoType;
import com.changhu.common.db.enums.ServiceProjectType;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@ -36,10 +37,13 @@ public class ServiceProjectPagerVo {
@Schema(description = "服务类型")
private ServiceProjectType type;
@Schema(description = "二级类型")
private ServiceProjectTwoType twoType;
@Schema(description = "外包公司名称")
private String outsourceName;
@Schema(description = "是否自招保安(只有当type为property 此字段必填)")
private IsOrNot isRecruitSecurity;
@Schema(description = "是否备案")
private IsOrNot isFiling;
@Schema(description = "证件号(服务类型为保安必填 服务类型为物业则需自招保安为必填)")
private String idNumber;

View File

@ -2,7 +2,6 @@ package com.changhu.module.miniProgram.pojo.vo;
import cn.hutool.core.lang.Dict;
import com.changhu.common.db.enums.EnterprisesUnitType;
import com.changhu.common.db.enums.IsOrNot;
import com.changhu.common.db.enums.ServiceProjectType;
import com.changhu.module.management.pojo.model.ContactPersonInfo;
import io.swagger.v3.oas.annotations.media.Schema;
@ -69,8 +68,6 @@ public class IndexServiceProjectListVo {
private String name;
@Schema(description = "服务项目类型")
private ServiceProjectType type;
@Schema(description = "是否自招保安(只有当type为property 此字段必填)")
private IsOrNot isRecruitSecurity;
@Schema(description = "证件号(服务类型为保安必填 服务类型为物业则需自招保安为必填)")
private String idNumber;
@Schema(description = "服务区域面积")

View File

@ -29,7 +29,7 @@ public class ServiceProjectSecurityUserPagerVo {
@Schema(description = "名称")
private String name;
@Desensitized(value = DesensitizedUtil.DesensitizedType.MOBILE_PHONE)
@Desensitized(value = DesensitizedUtil.DesensitizedType.MOBILE_PHONE, keepOriginalField = true)
@Schema(description = "手机号")
private String telephone;
@ -42,7 +42,7 @@ public class ServiceProjectSecurityUserPagerVo {
@Schema(description = "籍贯")
private String nativePlace;
@Desensitized(value = DesensitizedUtil.DesensitizedType.ID_CARD)
@Desensitized(value = DesensitizedUtil.DesensitizedType.ID_CARD, keepOriginalField = true)
@Schema(description = "身份证")
private String idCard;

View File

@ -0,0 +1,50 @@
package com.changhu.pojo.dto;
import com.changhu.common.db.enums.IsOrNot;
import com.changhu.common.db.enums.ServiceProjectTwoType;
import com.changhu.common.db.enums.ServiceProjectType;
import com.changhu.module.management.pojo.model.LegalPersonInfo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
/**
* @author 20252
* @createTime 2024/11/18 上午10:32
* @desc SecurityUnitUseStatisticsDTO...
*/
@Data
public class SecurityUnitUseStatisticsDTO {
@Schema(description = "服务项目id")
private Long serviceProjectId;
@Schema(description = "公安单位名称")
private String policeUnitName;
@Schema(description = "保安单位名称")
private String securityUnitName;
@Schema(description = "服务项目名称")
private String serviceProjectName;
@Schema(description = "保安服务类别")
private ServiceProjectType type;
@Schema(description = "二级类型")
private ServiceProjectTwoType twoType;
@Schema(description = "外包公司名称")
private String outsourceName;
@Schema(description = "保安人员总数")
private Integer securityUserTotal;
@Schema(description = "持证保安数量")
private Integer haveCardSecurityUserCount;
@Schema(description = "是否备案")
private IsOrNot isFiling;
@Schema(description = "证件号(保安服务许可证/备案证)")
private String idNumber;
@Schema(description = "保安单位法人信息")
private LegalPersonInfo securityUnitLegalPersonInfo;
@Schema(description = "项目经理")
private String serviceProjectManager;
@Schema(description = "项目经理联系方式")
private String serviceProjectManagerTelephone;
}

View File

@ -1,7 +1,6 @@
package com.changhu.pojo.dto;
import com.alibaba.fastjson2.annotation.JSONField;
import com.changhu.common.db.enums.IsOrNot;
import com.changhu.common.db.enums.ServiceProjectType;
import com.changhu.common.db.enums.Sex;
import com.changhu.module.management.pojo.model.LegalPersonInfo;
@ -29,8 +28,6 @@ public class ServiceProjectDTO {
private String name;
@Schema(description = "类型")
private ServiceProjectType type;
@Schema(description = "是否自招保安")
private IsOrNot isRecruitSecurity;
@Schema(description = "保安证号")
private String idNumber;
@Schema(description = "服务面积")

View File

@ -0,0 +1,36 @@
package com.changhu.pojo.dto;
import cn.hutool.core.util.DesensitizedUtil;
import com.changhu.common.annotation.Desensitized;
import com.changhu.common.db.enums.Sex;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
/**
* @author 20252
* @createTime 2024/11/18 上午10:43
* @desc ServiceProjectSecurityUserRosterDTO...
*/
@Data
public class ServiceProjectSecurityUserRosterDTO {
@Schema(description = "id")
private Long snowFlakeId;
@Schema(description = "名称")
private String name;
@Schema(description = "性别")
private Sex sex;
@Schema(description = "年龄")
private Integer age;
@Desensitized(value = DesensitizedUtil.DesensitizedType.ID_CARD, keepOriginalField = true)
@Schema(description = "身份证")
private String idCard;
@Desensitized(value = DesensitizedUtil.DesensitizedType.MOBILE_PHONE, keepOriginalField = true)
@Schema(description = "手机号")
private String telephone;
@Schema(description = "企事业单位名")
private String enterprisesUnitName;
@Schema(description = "保安单位名")
private String securityUnitName;
@Schema(description = "保安证号")
private String securityNumber;
}

View File

@ -0,0 +1,72 @@
package com.changhu.pojo.entity;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.extension.handlers.Fastjson2TypeHandler;
import com.changhu.common.db.enums.IsEnable;
import com.changhu.support.mybatisplus.pojo.entity.BaseEntity;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import lombok.experimental.SuperBuilder;
import java.io.Serial;
import java.io.Serializable;
import java.util.List;
/**
* 开放接口的授权 实体类
* author: luozhun
* desc 由groovy脚本自动生成
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
@SuperBuilder
@EqualsAndHashCode(callSuper = true)
@TableName(autoResultMap = true)
public class AccessKeys extends BaseEntity implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 调用方
*/
private String name;
/**
* 有效时间
*/
private Integer effectiveTime;
/**
* AccessKey
*/
private String accessKey;
/**
* SecretKey
*/
private String secretKey;
/**
* 允许访问的资源
*/
@TableField(typeHandler = Fastjson2TypeHandler.class)
private List<String> allowedResources;
/**
* 是否启用
*/
private IsEnable isEnable;
/**
* 备注
*/
private String remark;
}

View File

@ -0,0 +1,37 @@
package com.changhu.pojo.params;
import com.changhu.common.db.enums.IsEnable;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import java.util.List;
/**
* @author 20252
* @createTime 2024/11/19 上午11:19
* @desc GeneratedAccessKeyParams...
*/
@Data
public class GeneratedAccessKeyParams {
private Long snowFlakeId;
@Schema(description = "调用方")
@NotBlank(message = "调用方不能为空")
private String name;
@NotNull(message = "请输入有效时间")
@Schema(description = "有效时间")
private Integer effectiveTime;
@Schema(description = "允许访问的资源")
private List<String> allowedResources;
@NotNull
@Schema(description = "是否启用")
private IsEnable isEnable;
@Schema(description = "备注")
private String remark;
}

View File

@ -0,0 +1,20 @@
package com.changhu.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.changhu.pojo.entity.AccessKeys;
import com.changhu.pojo.params.GeneratedAccessKeyParams;
/**
* access_keys (开放接口的授权) 服务类
* author: luozhun
* desc 由groovy脚本自动生成
*/
public interface AccessKeysService extends IService<AccessKeys> {
/**
* 生成访问凭证
*
* @param params 参数
*/
void generatedAccessKey(GeneratedAccessKeyParams params);
}

View File

@ -3,7 +3,8 @@ package com.changhu.service;
import com.changhu.common.pojo.vo.SelectNodeVo;
import com.changhu.pojo.dto.DataViewDTO;
import com.changhu.pojo.dto.EnterprisesUnitDetailDTO;
import com.changhu.pojo.queryParams.OpenGetEnterprisesUnit;
import com.changhu.pojo.dto.SecurityUnitUseStatisticsDTO;
import com.changhu.pojo.dto.ServiceProjectSecurityUserRosterDTO;
import java.util.List;
@ -36,4 +37,21 @@ public interface OpenApiService {
* @return 数据总览
*/
DataViewDTO dataView();
/**
* 保安单位使用情况统计
*
* @param code 代码
* @param level 等级
* @return 使用情况
*/
List<SecurityUnitUseStatisticsDTO> securityUnitUseStatistics(String code, Integer level);
/**
* 服务项目安保人员花名册
*
* @param serviceProjectId 服务项目id
* @return 花名册
*/
List<ServiceProjectSecurityUserRosterDTO> serviceProjectUserRoster(Long serviceProjectId);
}

View File

@ -0,0 +1,41 @@
package com.changhu.service.impl;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.RandomUtil;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.changhu.mapper.AccessKeysMapper;
import com.changhu.pojo.entity.AccessKeys;
import com.changhu.pojo.params.GeneratedAccessKeyParams;
import com.changhu.service.AccessKeysService;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
/**
* access_keys (开放接口的授权) 服务实现类
* author: luozhun
* desc 由groovy脚本自动生成
*/
@Service
public class AccessKeysServiceImpl extends ServiceImpl<AccessKeysMapper, AccessKeys> implements AccessKeysService {
@Transactional(rollbackFor = Exception.class)
@Override
public void generatedAccessKey(GeneratedAccessKeyParams params) {
AccessKeys accessKeys = BeanUtil.copyProperties(params, AccessKeys.class);
if (accessKeys.getSnowFlakeId() == null) {
// 生成一个32位的随机字符串包含大小写字母和数字
String accessKey = RandomUtil.randomString(RandomUtil.BASE_CHAR + RandomUtil.BASE_NUMBER, 32);
String secretKey = IdUtil.randomUUID();
accessKeys.setAccessKey(accessKey);
accessKeys.setSecretKey(secretKey);
}
this.saveOrUpdate(accessKeys);
}
public static void main(String[] args) {
String randomPart = RandomUtil.randomString(RandomUtil.BASE_CHAR + RandomUtil.BASE_NUMBER, 32);
System.out.println(randomPart);
System.out.println(IdUtil.randomUUID());
}
}

View File

@ -11,7 +11,8 @@ import com.changhu.module.management.service.ServiceProjectService;
import com.changhu.module.miniProgram.pojo.entity.SecurityUser;
import com.changhu.pojo.dto.DataViewDTO;
import com.changhu.pojo.dto.EnterprisesUnitDetailDTO;
import com.changhu.pojo.queryParams.OpenGetEnterprisesUnit;
import com.changhu.pojo.dto.SecurityUnitUseStatisticsDTO;
import com.changhu.pojo.dto.ServiceProjectSecurityUserRosterDTO;
import com.changhu.service.OpenApiService;
import com.changhu.support.mybatisplus.pojo.entity.BaseEntity;
import lombok.SneakyThrows;
@ -95,4 +96,14 @@ public class OpenApiServiceImpl implements OpenApiService {
countDownLatch.await();
return dataViewDTO;
}
@Override
public List<SecurityUnitUseStatisticsDTO> securityUnitUseStatistics(String code, Integer level) {
return openApiMapper.securityUnitUseStatistics(code, level);
}
@Override
public List<ServiceProjectSecurityUserRosterDTO> serviceProjectUserRoster(Long serviceProjectId) {
return openApiMapper.serviceProjectUserRoster(serviceProjectId);
}
}

View File

@ -1,12 +1,16 @@
package com.changhu.support.fastjson2.filter;
import cn.hutool.core.annotation.AnnotationUtil;
import cn.hutool.core.util.ClassUtil;
import cn.hutool.core.util.DesensitizedUtil;
import com.alibaba.fastjson2.filter.ValueFilter;
import com.changhu.common.annotation.Desensitized;
import com.changhu.common.pojo.vo.DesensitizedVo;
import lombok.extern.slf4j.Slf4j;
import java.lang.reflect.Field;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
/**
@ -19,41 +23,62 @@ public class DesensitizedFilter implements ValueFilter {
public static final DesensitizedFilter instance = new DesensitizedFilter();
private final Map<String, DesensitizedUtil.DesensitizedType> cache = new ConcurrentHashMap<>();
private static final Map<FieldKey, Desensitized> cache = new ConcurrentHashMap<>();
private DesensitizedFilter() {
}
static {
preProcessAnnotations();
}
@Override
public Object apply(Object object, String name, Object value) {
//只针对string类型生效
if (value instanceof String strValue && !strValue.isEmpty()) {
try {
String path = object.getClass().getName() + "$$" + name;
//先看是否有缓存
if (cache.containsKey(path)) {
return DesensitizedUtil.desensitized(strValue, cache.get(path));
FieldKey fieldKey = new FieldKey(object.getClass(), name);
Desensitized desensitized = cache.get(fieldKey);
if (desensitized != null) {
if (desensitized.keepOriginalField()) {
return DesensitizedVo.builder()
.originalValue(strValue)
.desensitizedValue(DesensitizedUtil.desensitized(strValue, desensitized.value()))
.build();
}
return DesensitizedUtil.desensitized(strValue, desensitized.value());
}
}
Class<?> aClass = object.getClass();
Field declaredField = aClass.getDeclaredField(name);
Desensitized annotation = declaredField.getAnnotation(Desensitized.class);
if (annotation == null) {
return value;
}
//加入缓存
cache.put(path, annotation.value());
return DesensitizedUtil.desensitized(strValue, annotation.value());
} catch (NoSuchFieldException e) {
return value;
} catch (SecurityException e) {
log.error("字段:{} 安全异常: {}", name, e.getMessage());
return null;
} catch (Exception e) {
log.error("数据脱敏失败:{}", e.getMessage());
return null;
/**
* 脱敏字段预处理 初始化缓存信息
*/
private static void preProcessAnnotations() {
Set<Class<?>> classes = ClassUtil.scanPackage("com.changhu");
for (Class<?> clazz : classes) {
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
Desensitized annotation = AnnotationUtil.getAnnotation(field, Desensitized.class);
if (annotation != null) {
cache.put(new FieldKey(clazz, field.getName()), annotation);
}
} else {
return value;
}
}
}
private record FieldKey(Class<?> clazz, String fieldName) {
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
FieldKey fieldKey = (FieldKey) o;
return clazz.equals(fieldKey.clazz) && fieldName.equals(fieldKey.fieldName);
}
@Override
public int hashCode() {
return clazz.hashCode() ^ fieldName.hashCode();
}
}

View File

@ -28,4 +28,73 @@
</choose>
order by eu.create_time desc
</select>
<resultMap id="SecurityUnitUseStatisticsDTOResultMap" type="com.changhu.pojo.dto.SecurityUnitUseStatisticsDTO">
<result
column="securityUnitLegalPersonInfo"
typeHandler="com.baomidou.mybatisplus.extension.handlers.Fastjson2TypeHandler"
property="securityUnitLegalPersonInfo"/>
</resultMap>
<select id="securityUnitUseStatistics" resultMap="SecurityUnitUseStatisticsDTOResultMap">
select
sp.snow_flake_id as 'serviceProjectId',
pu.name as 'policeUnitName',
su.name as 'securityUnitName',
sp.name as 'serviceProjectName',
sp.type as 'type',
sp.two_type as 'twoType',
sp.outsource_name as 'outsourceName',
count(suu.snow_flake_id) as 'securityUserTotal',
SUM(IF(suu.security_number != '', 1, 0)) as 'haveCardSecurityUserCount',
sp.is_filing as 'isFiling',
sp.id_number,
su.legal_person_info as 'securityUnitLegalPersonInfo',
mpu.name as 'serviceProjectManagerName',
mpu.telephone as 'serviceProjectManagerTelephone'
from police_unit pu
join enterprises_unit eu on pu.snow_flake_id = eu.police_unit_id and eu.delete_flag = 0
join service_project sp on eu.snow_flake_id = sp.enterprises_unit_id and sp.delete_flag = 0
join security_unit su on sp.security_unit_id = su.snow_flake_id and su.delete_flag = 0
LEFT join mini_program_user mpu on sp.project_manager_mini_program_user_id = mpu.snow_flake_id
LEFT join security_user suu on suu.service_project_id = sp.snow_flake_id and suu.delete_flag = 0
where pu.delete_flag = 0
<choose>
<when test="level==1">
and eu.province = #{code}
</when>
<when test="level==2">
and eu.city = #{code}
</when>
<when test="level==3">
and eu.districts = #{code}
</when>
<when test="level==4">
and eu.street = #{code}
</when>
<when test="level==5">
and pu.code = #{code}
</when>
<otherwise>and eu.snow_flake_id = -1</otherwise>
</choose>
group by sp.snow_flake_id
order by sp.create_time desc
</select>
<select id="serviceProjectUserRoster"
resultType="com.changhu.pojo.dto.ServiceProjectSecurityUserRosterDTO">
select su.snow_flake_id,
su.name,
su.sex,
TIMESTAMPDIFF(YEAR, su.date_of_birth, CURDATE()) AS 'age',
su.id_card,
su.telephone,
eu.name as 'enterprisesUnitName',
suu.name as 'securityUnitName',
su.security_number
from enterprises_unit eu
left join service_project sp on sp.enterprises_unit_id = eu.snow_flake_id and sp.delete_flag = 0
left join security_user su on su.service_project_id = sp.snow_flake_id and su.delete_flag = 0
left join security_unit suu on su.security_unit_id = suu.snow_flake_id and suu.delete_flag = 0
where eu.delete_flag = 0
and sp.snow_flake_id = #{serviceProjectId}
order by su.create_time desc
</select>
</mapper>

View File

@ -52,7 +52,6 @@
'securityUnitName',su.name,
'name', sp.name,
'type', sp.type,
'isRecruitSecurity', sp.is_recruit_security,
'idNumber', sp.id_number,
'serviceArea', sp.service_area,
'buildingTotal', sp.building_total,
@ -65,7 +64,7 @@
'idCard',mpu.id_card),
'remark', sp.remark)) as 'service_project_list'
from enterprises_unit eu
join service_project sp on eu.snow_flake_id = sp.enterprises_unit_id and sp.delete_flag = 0
left join service_project sp on eu.snow_flake_id = sp.enterprises_unit_id and sp.delete_flag = 0
left join security_unit su on sp.security_unit_id = su.snow_flake_id and su.delete_flag = 0
left join mini_program_user mpu on sp.project_manager_mini_program_user_id = mpu.snow_flake_id and mpu.delete_flag = 0
left join administrative_division ad1 on eu.province = ad1.code and ad1.delete_flag = 0

View File

@ -59,5 +59,11 @@ export const SYSTEM_MENUS: SystemMenu[] = [
type: 'menu',
isFull: true,
component: () => import('@/views/data/dataOverview.vue')
}, {
title: '开放平台',
name: 'openPlatform',
path: '/openPlatform',
type: 'menu',
component: () => import('@/views/openPlatform/index.vue')
}
]

View File

@ -62,3 +62,11 @@ interface BaseEnum<T> {
label: string,
extData?: Record<string, any>
}
/**
*
*/
interface DesensitizedVo {
originalValue: string;
desensitizedValue: string;
}

View File

@ -20,7 +20,7 @@ export const useUserStore = defineStore({
getTokenInfo: (state): TokenInfo => state.tokenInfo as TokenInfo,
},
persist: {
key: "useUserStore",
key: "spUserStore",
storage: window.localStorage,
paths: ["tokenInfo"],
}

View File

@ -0,0 +1,28 @@
// Parameter interface
import {BaseTableRowRecord} from "@/types/components/table";
export interface GeneratedAccessKeyParams {
/* */
snowFlakeId?: string;
/*调用方 */
name: string;
/*有效时间 */
effectiveTime: number;
/*允许访问的资源 */
allowedResources?: string[];
/*是否启用,可用值:TRUE,FALSE */
isEnable: number;
/*备注 */
remark?: string;
}
// Response interface
export interface AccessKeyRes extends BaseTableRowRecord {
name: string;
effectiveTime: number;
accessKey: string;
secretKey: string;
allowedResources: string[];
isEnable: BaseEnum<number>;
remark: string;
}

View File

@ -0,0 +1,163 @@
<template>
<table-pro-max
ref="tableRef"
:request-api="reqApi"
:is-pagination="false"
:columns="columns"
>
<template #tableHeader>
<a-space>
<a-button class="btn-success" @click="saveOrUpdateAccessKey({
name: '',
effectiveTime: 300000,
isEnable:0
})">添加accessKey
</a-button>
<a-button @click="a">a</a-button>
</a-space>
</template>
</table-pro-max>
</template>
<script setup lang="tsx">
import {message} from "ant-design-vue";
import api from "@/axios";
import {onMounted, ref} from "vue";
import {submitSimpleFormModal} from "@/components/tsx/ModalPro.tsx";
import {dictSelectNodes} from "@/config/dict.ts";
import TableProMax from "@/components/table/TableProMax.vue";
import {TableProMaxProps} from "@/types/components/table";
import {AccessKeyRes, GeneratedAccessKeyParams} from "@/types/views/openPlatform/openPlatform.ts";
import {ComponentExposed} from "vue-component-type-helpers";
import axios from "axios";
import {useUserStore} from "@/stores/modules/userStore.ts";
type TableProps = TableProMaxProps<AccessKeyRes>
const tableRef = ref<ComponentExposed<typeof TableProMax>>(null)
const columns: TableProps['columns'] = [
{
dataIndex: 'name',
title: '调用方'
}, {
dataIndex: 'accessKey',
title: 'accessKey'
}, {
dataIndex: 'secretKey',
title: 'secretKey'
}, {
dataIndex: 'effectiveTime',
title: '有效时间(ms)'
}, {
dataIndex: 'isEnable',
title: '状态',
customRender: ({text}) => <a-tag color={text.extData?.color}>{text.label}</a-tag>
}, {
dataIndex: 'remark',
title: '备注',
}, {
dataIndex: 'createTime',
title: '创建时间'
}, {
dataIndex: 'opt',
title: '操作',
customRender: ({record}) => {
return <a-space>
<a-button class="btn-warn" onClick={() => saveOrUpdateAccessKey({
snowFlakeId: record.snowFlakeId,
name: record.name,
effectiveTime: record.effectiveTime,
isEnable: record.isEnable.value,
allowedResources: record.allowedResources,
remark: record.remark
})}>编辑
</a-button>
</a-space>
}
}
]
const reqApi: TableProps["requestApi"] = () => api.get('/accessKeys/tableList')
const saveOrUpdateAccessKey = (params: GeneratedAccessKeyParams) => {
submitSimpleFormModal<GeneratedAccessKeyParams>({
title: '',
formOptions: {
name: {
type: 'input',
label: '调用方',
required: true
},
effectiveTime: {
type: 'inputNumber',
label: '有效时间(ms)',
required: true,
componentsProps: {
precision: 0
}
},
allowedResources: {
type: 'select',
label: '可访问资源',
options: allowedResources.value,
componentsProps: {
mode: 'multiple'
}
},
isEnable: {
type: "radioGroup",
label: '是否启用',
options: dictSelectNodes("IsEnable")
},
remark: {
type: 'inputTextArea',
label: '备注'
}
},
formParams: {...params},
submit: async (p) => {
const resp = await api.post('/accessKeys/generatedAccessKey', p, {loading: true})
message.success(resp.message)
await tableRef.value?.requestGetTableData()
}
})
}
const userStore = useUserStore()
const a = () => {
axios.get('http://127.0.0.1:8765/open/dataView', {
headers: {
'X-API-KEY': '123',
'access-key': '123',
'time-stamp': '123',
'sign': '123',
'nonce': '123'
}
}).then(resp => {
console.log(resp);
})
// api.get('/open/dataView', null, {
// headers: {
// 'X-API-KEY': '123',
// 'access-key': '123',
// 'time-stamp': '123',
// 'sign': '123',
// 'nonce': '123'
// }
// })
}
const allowedResources = ref<SelectNodeVo<string>[]>([])
onMounted(() => {
api.get<SelectNodeVo<string>[]>('/accessKeys/listOpenApi').then(resp => {
allowedResources.value = resp.data
})
})
</script>
<style scoped lang="scss">
</style>