Przeglądaj źródła

风控规则优化

marxjaw 4 miesięcy temu
rodzic
commit
340ab5627a
21 zmienionych plików z 118 dodań i 44 usunięć
  1. 2 2
      yt-agent/agent-service/src/main/java/com/ytpm/controller/YtAppUserController.java
  2. 1 1
      yt-agent/agent-service/src/main/java/com/ytpm/service/YtAppUserService.java
  3. 2 2
      yt-agent/agent-service/src/main/java/com/ytpm/service/impl/YtAppUserServiceImpl.java
  4. 1 1
      yt-app/app-feign/src/main/java/com/ytpm/feign/AppFeign.java
  5. 2 2
      yt-app/app-service/src/main/java/com/ytpm/controller/UserController.java
  6. 1 1
      yt-app/app-service/src/main/java/com/ytpm/dao/AdRecordMapper.java
  7. 3 3
      yt-app/app-service/src/main/resources/mapper/AdRecordMapper.xml
  8. 8 8
      yt-common/src/main/java/com/ytpm/app/model/DefaultRiskConfig.java
  9. 0 1
      yt-common/src/main/java/com/ytpm/app/model/YtDyzAnswerRecord.java
  10. 3 3
      yt-common/src/main/java/com/ytpm/app/model/YtDyzLoginRecord.java
  11. 8 8
      yt-common/src/main/java/com/ytpm/app/model/YtDyzUser.java
  12. 5 5
      yt-common/src/main/java/com/ytpm/app/view/YtAppUserListView.java
  13. 4 0
      yt-common/src/main/java/com/ytpm/custom/CustomField.java
  14. 1 0
      yt-common/src/main/java/com/ytpm/risk/model/YtRiskTemplate.java
  15. 2 0
      yt-common/src/main/java/com/ytpm/risk/param/RiskConfigParam.java
  16. 2 0
      yt-common/src/main/java/com/ytpm/risk/view/RiskConfigListView.java
  17. 2 0
      yt-common/src/main/java/com/ytpm/risk/view/RiskTemplateView.java
  18. 5 0
      yt-risk/risk-manage/src/main/java/com/ytpm/dao/RiskConfigMapper.java
  19. 5 0
      yt-risk/risk-manage/src/main/java/com/ytpm/service/RiskService.java
  20. 29 6
      yt-risk/risk-manage/src/main/java/com/ytpm/service/impl/RiskServiceImpl.java
  21. 32 1
      yt-risk/risk-manage/src/main/resources/mapper/RiskConfigMapper.xml

+ 2 - 2
yt-agent/agent-service/src/main/java/com/ytpm/controller/YtAppUserController.java

@@ -53,7 +53,7 @@ public class YtAppUserController {
      */
     @GetMapping("/ecpm")
     @ApiOperation(value = "查询用户ecpm数据")
-    public ResultTable<YtUserEcpmListView> ecpmList(@RequestParam(value = "userId", required = true)String userId,@RequestParam(value = "adSourceType",required = false) Integer adSourceType){
-        return appUserService.ecpmList(userId,adSourceType);
+    public ResultTable<YtUserEcpmListView> ecpmList(@RequestParam(name = "userId", required = true) String userId){
+        return appUserService.ecpmList(userId);
     }
 }

+ 1 - 1
yt-agent/agent-service/src/main/java/com/ytpm/service/YtAppUserService.java

@@ -25,5 +25,5 @@ public interface YtAppUserService {
     /**
      * 查询用户的ecpm列表
      */
-    ResultTable<YtUserEcpmListView> ecpmList(String userId, Integer adSourceType);
+    ResultTable<YtUserEcpmListView> ecpmList(String userId);
 }

+ 2 - 2
yt-agent/agent-service/src/main/java/com/ytpm/service/impl/YtAppUserServiceImpl.java

@@ -141,8 +141,8 @@ public class YtAppUserServiceImpl implements YtAppUserService {
      * 查询用户的ecpm
      */
     @Override
-    public ResultTable<YtUserEcpmListView> ecpmList(String userId,Integer adSourceType) {
-        ResultTable<YtDyzAdRecord> table = appFeign.adRecords(userId,adSourceType);
+    public ResultTable<YtUserEcpmListView> ecpmList(String userId) {
+        ResultTable<YtDyzAdRecord> table = appFeign.adRecords(userId);
         List<YtDyzAdRecord> data = table.getData();
         return ResultTable.resultTableOk(new PageInfo<>(data));
     }

+ 1 - 1
yt-app/app-feign/src/main/java/com/ytpm/feign/AppFeign.java

@@ -26,7 +26,7 @@ public interface AppFeign {
     @PostMapping("/user/updateUserInfo")
     Result<?> updateUserInfo(@RequestBody YtDyzUser dyzUser);
     @GetMapping("/user/adRecords")
-    ResultTable<YtDyzAdRecord> adRecords(@RequestParam(value = "userId",required = true) String userId,@RequestParam(value = "adSourceType",required = false) Integer adSourceType);
+    ResultTable<YtDyzAdRecord> adRecords(@RequestParam(name = "userId",required = true) String userId);
 
     @PostMapping("/user/queryUserByTime")
     List<YtDyzUser> queryUserByTime(@RequestBody AppUserQueryParam appUserQueryParam);

+ 2 - 2
yt-app/app-service/src/main/java/com/ytpm/controller/UserController.java

@@ -189,8 +189,8 @@ public class UserController {
      * 查询用户的广告记录
      */
     @GetMapping("/adRecords")
-    public ResultTable<YtDyzAdRecord> adRecords(@RequestParam(value = "userId",required = true) String userId,@RequestParam(value = "adSourceType",required = false) Integer adSourceType) {
-        return ResultTable.resultTableOk(new PageInfo<YtDyzAdRecord>(adRecordMapper.getByUserId(userId,adSourceType)));
+    public ResultTable<YtDyzAdRecord> adRecords(@RequestParam(name = "userId",required = true) String userId) {
+        return ResultTable.resultTableOk(new PageInfo<YtDyzAdRecord>(adRecordMapper.getByUserId(userId)));
     }
 
     @PostMapping("/queryUserByTime")

+ 1 - 1
yt-app/app-service/src/main/java/com/ytpm/dao/AdRecordMapper.java

@@ -15,7 +15,7 @@ public interface AdRecordMapper {
     /**
      * 查询用户的广告记录
      */
-    List<YtDyzAdRecord> getByUserId(@Param("userId") String userId, @Param("adSourceType")Integer adSourceType);
+    List<YtDyzAdRecord> getByUserId(@Param("userId") String userId);
 
     List<YtDyzAdRecord> getByUserIds(@Param("userIds") String userIds);
 }

+ 3 - 3
yt-app/app-service/src/main/resources/mapper/AdRecordMapper.xml

@@ -45,9 +45,9 @@
             record_id, user_id, nick_name, placement_id, ad_source_id, revenue, network_form_id, network_name, network_placement_id, finish_time, begin_time,result_json,ad_source_type,ad_source_index,ecpm
         from yt_dyz_ad_record
         where user_id = #{userId}
-          <if test="adSourceType != null">
-              and ad_source_type = #{adSourceType}
-          </if>
+<!--          <if test="adSourceType != null">-->
+<!--              and ad_source_type = #{adSourceType}-->
+<!--          </if>-->
         order by finish_time desc
     </select>
     <select id="getByUserIds" resultType="com.ytpm.app.model.YtDyzAdRecord">

+ 8 - 8
yt-common/src/main/java/com/ytpm/app/model/DefaultRiskConfig.java

@@ -17,30 +17,30 @@ import java.math.BigDecimal;
 @AllArgsConstructor
 @NoArgsConstructor
 public class DefaultRiskConfig {
-    @CustomField
+    @CustomField(desc = "?天内",node = 1)
     @ApiModelProperty("?天内")
     private int days;
-    @CustomField
+    @CustomField(desc = "IP前?段",node = 1)
     @ApiModelProperty("IP前?段")
     private int ipPrefix;
-    @CustomField
+    @CustomField(node = 1)
     @ApiModelProperty("同IP登录数量")
     private int ipSameCount;
-    @CustomField
+    @CustomField(node = 2)
     @ApiModelProperty("当日前?条激励视频")
     private int firstAdCount;
-    @CustomField
+    @CustomField(node = 2)
     @ApiModelProperty("ecpm值")
     private int ecpm;
-    @CustomField
+    @CustomField(node = 2)
     @ApiModelProperty("总收益")
     private BigDecimal income;
 
-    @CustomField(desc = "当日前?条获得奖励的激励视频")
+    @CustomField(desc = "当日前?条获得奖励的激励视频",node = 2)
     @ApiModelProperty("当日前?条获得奖励的激励视频")
     private int rewardCount;
 
-    @CustomField(desc = "有?条")
+    @CustomField(desc = "有?条", node = 2)
     @ApiModelProperty("有?条")
     private int haveCount;
 

+ 0 - 1
yt-common/src/main/java/com/ytpm/app/model/YtDyzAnswerRecord.java

@@ -16,7 +16,6 @@ public class YtDyzAnswerRecord {
     private String questionId;
     @ApiModelProperty("答案ID")
     private String itemId;
-    @CustomField
     @ApiModelProperty("答题时长")
     private Long duration;
     @ApiModelProperty("答题时间")

+ 3 - 3
yt-common/src/main/java/com/ytpm/app/model/YtDyzLoginRecord.java

@@ -21,7 +21,7 @@ public class YtDyzLoginRecord {
     @ApiModelProperty("用户ID")
     private String userId;
     /** 登录时间 */
-    @CustomField
+    @CustomField(node = 1)
     @ApiModelProperty("登录时间")
     private Date loginTime;
     /** 设备品牌 */
@@ -34,11 +34,11 @@ public class YtDyzLoginRecord {
     @ApiModelProperty("登录IP")
     private String loginIp;
     /** 运营商 */
-    @CustomField
+    @CustomField(node = 1)
     @ApiModelProperty("运营商")
     private String operator;
     /** IP归属地 */
-    @CustomField
+    @CustomField(node = 1)
     @ApiModelProperty("IP归属地")
     private String ipAddr;
 }

+ 8 - 8
yt-common/src/main/java/com/ytpm/app/model/YtDyzUser.java

@@ -32,7 +32,7 @@ public class YtDyzUser extends PageMeta {
     @ApiModelProperty("用户头像")
     private String headImg;
     /** 注册时间 */
-    @CustomField
+    @CustomField(node = 1)
     @ApiModelProperty("注册时间")
     private Date registryTime;
     /** 最新登录时间 */
@@ -42,39 +42,39 @@ public class YtDyzUser extends PageMeta {
     @ApiModelProperty("最新登录IP")
     private String lastLoginIp;
     /** 登录天数 */
-    @CustomField
+    @CustomField(node = 1)
     @ApiModelProperty("登录天数")
     private Integer loginDays;
     /** 总观看视频数 */
-    @CustomField
+    @CustomField(node = 1)
     @ApiModelProperty("总观看视频数")
     private Integer totalVideo;
     /** 总收益 */
     @ApiModelProperty("总收益")
     private BigDecimal totalIncome;
     /** 红包余额 */
-    @CustomField
+    @CustomField(node = 1)
     @ApiModelProperty("红包余额")
     private BigDecimal redPacketBalance;
     /** 红包总金额 */
     @ApiModelProperty("红包总金额")
     private BigDecimal redPacketAmount;
     /** 积分余额 */
-    @CustomField
+    @CustomField(node = 1)
     @ApiModelProperty("积分余额")
     private BigDecimal pointsBalance;
     /** 积分总额 */
     @ApiModelProperty("积分总额")
     private BigDecimal pointsTotal;
     /** 提现总额 */
-    @CustomField
+    @CustomField(node = 1)
     @ApiModelProperty("提现总额")
     private BigDecimal withdrawTotal;
     /** 用户签到天数 */
     @ApiModelProperty("用户签到天数")
     private Integer signDays;
     /** 用户状态 */
-    @CustomField(value = "user_status",desc = "用户状态")
+    @CustomField(value = "user_status",desc = "用户状态",node = 1)
     @ApiModelProperty("用户状态")
     private Integer userStatus;
     /** 风控原因 */
@@ -93,7 +93,7 @@ public class YtDyzUser extends PageMeta {
     @ApiModelProperty("体力")
     private Integer power;
     /** 今日答题数 */
-    @CustomField
+    @CustomField(node = 1)
     @ApiModelProperty("今日答题数")
     private Integer todayAnswerCount;
     /** 历史答题数 */

+ 5 - 5
yt-common/src/main/java/com/ytpm/app/view/YtAppUserListView.java

@@ -48,10 +48,10 @@ public class YtAppUserListView extends PageMeta {
     private Integer channelType;
     @ApiModelProperty("渠道来源")
     private Integer channelOrigin;
-    @CustomField(value = "user_type",desc = "用户类型")
+    @CustomField(value = "user_type",desc = "用户类型", node = 1)
     @ApiModelProperty("用户类型")
     private Integer userType;
-    @CustomField(value = "user_status",desc = "用户状态")
+    @CustomField(value = "user_status",desc = "用户状态", node = 1)
     @ApiModelProperty("用户状态")
     private Integer userStatus;
     @ApiModelProperty("今日视频播放数")
@@ -60,7 +60,7 @@ public class YtAppUserListView extends PageMeta {
     private Integer totalVideo;
     @ApiModelProperty("登录天数")
     private Integer loginDays;
-    @CustomField
+    @CustomField(node = 1)
     @ApiModelProperty("总收益")
     private BigDecimal totalIncome;
     @ApiModelProperty("红包余额")
@@ -75,10 +75,10 @@ public class YtAppUserListView extends PageMeta {
     private BigDecimal withdrawTotal;
     @ApiModelProperty("提现笔数")
     private Integer withdrawCount;
-    @CustomField
+    @CustomField(node = 1)
     @ApiModelProperty("IP重复数量")
     private Integer ipRepeatCount;
-    @CustomField
+    @CustomField(node = 1)
     @ApiModelProperty("设备重复数量")
     private Integer deviceRepeatCount;
     @ApiModelProperty("通信运营商")

+ 4 - 0
yt-common/src/main/java/com/ytpm/custom/CustomField.java

@@ -9,6 +9,10 @@ import java.lang.annotation.Target;
 @Target(ElementType.FIELD)
 @Retention(RetentionPolicy.RUNTIME)
 public @interface CustomField {
+    /** 注解携带值 */
     String value() default "";
+    /** 注解字段描述 */
     String desc() default "";
+    /** 业务节点 1-登录 2-广告 */
+    int node();
 }

+ 1 - 0
yt-common/src/main/java/com/ytpm/risk/model/YtRiskTemplate.java

@@ -17,6 +17,7 @@ public class YtRiskTemplate {
     private String templateContent;
     private String templateCode;
     private String channelId;
+    private Integer effectNode;
     private String appId;
     private Date createTime;
     private String createUserId;

+ 2 - 0
yt-common/src/main/java/com/ytpm/risk/param/RiskConfigParam.java

@@ -36,4 +36,6 @@ public class RiskConfigParam extends BaseParam {
     private String operatorName;
     @ApiModelProperty(value = "是否满足所有 1-全部 2-任意一条即可")
     private Integer allSatisfy;
+    @ApiModelProperty(value = "业务节点")
+    private Integer effectNode;
 }

+ 2 - 0
yt-common/src/main/java/com/ytpm/risk/view/RiskConfigListView.java

@@ -46,4 +46,6 @@ public class RiskConfigListView extends PageMeta {
     private Integer canModify;
     @ApiModelProperty("条件范围")
     private Integer allSatisfy;
+    @ApiModelProperty("影响节点")
+    private Integer effectNode;
 }

+ 2 - 0
yt-common/src/main/java/com/ytpm/risk/view/RiskTemplateView.java

@@ -38,6 +38,8 @@ public class RiskTemplateView {
     private Integer enabled;
     @ApiModelProperty("是否启用")
     private Integer allSatisfy;
+    @ApiModelProperty("影响节点")
+    private Integer effectNode;
     @ApiModelProperty("配置项列表")
     private List<RiskConfigView> configList;
 }

+ 5 - 0
yt-risk/risk-manage/src/main/java/com/ytpm/dao/RiskConfigMapper.java

@@ -74,4 +74,9 @@ public interface RiskConfigMapper {
      * 根据应用ID获取应用所有者
      */
     String getApplicationOwner(@Param("appId") String appId);
+
+    /**
+     * 根据effect_node 获取不同业务节点风控规则
+     */
+    RiskTemplateView getByNode(@Param("effectNode") Integer effectNode);
 }

+ 5 - 0
yt-risk/risk-manage/src/main/java/com/ytpm/service/RiskService.java

@@ -27,6 +27,11 @@ public interface RiskService {
      * 查询配置字段选项
      */
     ResultTable<RiskConfigView> getRiskConfig(String appIds);
+
+    /**
+     * 根据业务节点获取字段配置
+     */
+    ResultTable<RiskConfigView> getFieldConfigByNode(Integer effectNode);
     /**
      * 保存风控配置
      */

+ 29 - 6
yt-risk/risk-manage/src/main/java/com/ytpm/service/impl/RiskServiceImpl.java

@@ -102,7 +102,25 @@ public class RiskServiceImpl extends ReflectUtil implements RiskService {
         List<RiskConfigView> configList = new ArrayList<RiskConfigView>();
         try{
             for (AgentAppClassView view : viewList) {
-                addConfigItem(Class.forName(view.getFullName()).getDeclaredFields(),configList);
+                addConfigItem(Class.forName(view.getFullName()).getDeclaredFields(),configList,null);
+            }
+        }catch (Exception e){
+            log.error(e.getMessage());
+            throw new CustomerException(e.getMessage());
+        }
+        return ResultTable.resultTableOk(new PageInfo<RiskConfigView>(configList));
+    }
+
+    /**
+     * 根据业务节点查询配置项
+     */
+    @Override
+    public ResultTable<RiskConfigView> getFieldConfigByNode(Integer effectNode) {
+        List<AgentAppClassView> viewList = configMapper.getAppClazz("");
+        List<RiskConfigView> configList = new ArrayList<RiskConfigView>();
+        try{
+            for (AgentAppClassView view : viewList) {
+                addConfigItem(Class.forName(view.getFullName()).getDeclaredFields(),configList,effectNode);
             }
         }catch (Exception e){
             log.error(e.getMessage());
@@ -114,14 +132,15 @@ public class RiskServiceImpl extends ReflectUtil implements RiskService {
     /**
      * 添加配置项
      */
-    private void addConfigItem(Field[] fields, List<RiskConfigView> configList) {
+    private void addConfigItem(Field[] fields, List<RiskConfigView> configList, Integer effectNode) {
         RiskConfigView view;
         for (Field field : fields) {
             field.setAccessible(true);
+            if(Objects.nonNull(effectNode) && field.getAnnotation(CustomField.class).node() != effectNode)continue;
             if(!field.isAnnotationPresent(CustomField.class))continue;
             view = new RiskConfigView();
             view.setFieldName(field.getName());
-            if(StrUtil.isNotBlank(field.getAnnotation(CustomField.class).value())){
+            if(CharSequenceUtil.isNotBlank(field.getAnnotation(CustomField.class).value())){
                 view.setDictList(dictMapper.getByTypeCode(field.getAnnotation(CustomField.class).value()));
             }
             view.setFieldDesc(field.getAnnotation(ApiModelProperty.class).value());
@@ -407,8 +426,10 @@ public class RiskServiceImpl extends ReflectUtil implements RiskService {
     @Override
     public Result<?> checkRisk(YtDyzUser dyzUser) {
         RiskTemplateView view = configMapper.getByCode(dyzUser.getRiskCode());
-        //校验默认的风控配置
-        checkDefaultRiskConfig(dyzUser, view.getConfigList());
+        //如果启用了该配置 校验默认的风控配置
+        if(1==view.getEnabled()){
+            checkDefaultRiskConfig(dyzUser, view.getConfigList());
+        }
         //查询用户所在app是否配置其他风控规则
 
         return Result.resultOk(RepMessage.QUERY_SUCCESS);
@@ -468,7 +489,7 @@ public class RiskServiceImpl extends ReflectUtil implements RiskService {
      */
     @Override
     public Result<?> checkAdRisk(YtDyzUser dyzUser) {
-        ResultTable<YtDyzAdRecord> table = appFeign.adRecords(dyzUser.getUserId(),null);
+        ResultTable<YtDyzAdRecord> table = appFeign.adRecords(dyzUser.getUserId());
         List<YtDyzAdRecord> records = table.getData();
         if(records.isEmpty()){
             return Result.resultOk(RepMessage.QUERY_SUCCESS);
@@ -494,6 +515,7 @@ public class RiskServiceImpl extends ReflectUtil implements RiskService {
      */
     private void checkRisk746(YtDyzUser dyzUser, List<YtDyzAdRecord> records) {
         RiskTemplateView revenue = configMapper.getByCode("746");
+        if(revenue.getEnabled()!=1)return;
         Map<String, String> revenueMap = revenue.getConfigList().stream().collect(
                 Collectors.toMap(RiskConfigView::getFieldName, RiskConfigView::getConfigVal));
         List<YtDyzAdRecord> revenues = records.stream().filter(
@@ -516,6 +538,7 @@ public class RiskServiceImpl extends ReflectUtil implements RiskService {
      * 校验默认风控规则742
      */
     private void checkRisk742(RiskTemplateView ecpmLimit,YtDyzUser dyzUser,List<YtDyzAdRecord> records) {
+        if(ecpmLimit.getEnabled()!=1)return;
         Map<String, String> limitMap = ecpmLimit.getConfigList().stream().collect(
                 Collectors.toMap(RiskConfigView::getFieldName, RiskConfigView::getConfigVal));
         int adCount = 0;

+ 32 - 1
yt-risk/risk-manage/src/main/resources/mapper/RiskConfigMapper.xml

@@ -33,6 +33,7 @@
              template_name,
              template_content,
              template_code,
+             effect_node,
              channel_id,
              all_satisfy,
              app_id,
@@ -46,6 +47,7 @@
                 #{templateName},
                 #{templateContent},
                 #{templateCode},
+                #{effectNode},
                 #{channelId},
                 #{allSatisfy},
                 #{appId},
@@ -89,6 +91,9 @@
             <if test="channelId != null">
                 channel_id = #{channelId},
             </if>
+            <if test="effectNode != null">
+                effect_node = #{effectNode},
+            </if>
             <if test="appId != null">
                 app_id = #{appId},
             </if>
@@ -118,6 +123,7 @@
             t.template_content,
             u.nick_name as nickName,
             t.enabled,
+            t.effect_node,
             t.all_satisfy,
             t.can_modify,
             t.update_time,
@@ -133,7 +139,7 @@
     </select>
     <select id="selectOneTemplate" resultType="com.ytpm.risk.model.YtRiskTemplate">
         select
-            template_id, template_name, template_content, template_code, channel_id, app_id, create_time, create_user_id, update_time, update_user_id, enabled
+            template_id, template_name, template_content, template_code,effect_node, channel_id, app_id, create_time, create_user_id, update_time, update_user_id, enabled
         from yt_risk_template
         where template_id = #{templateId}
     </select>
@@ -144,6 +150,7 @@
         <result column="template_content" property="templateContent" />
         <result column="enabled" property="enabled" />
         <result column="all_satisfy" property="allSatisfy" />
+        <result column="effect_node" property="effectNode" />
         <collection property="configList" ofType="com.ytpm.risk.view.RiskConfigView">
             <id column="config_id" property="configId" />
             <result column="field_name" property="fieldName" />
@@ -159,6 +166,7 @@
             rt.template_code,
             rt.template_name,
             rt.template_content,
+            rt.effect_node,
             rt.enabled,
             rt.all_satisfy,
             rc.config_id,
@@ -181,6 +189,7 @@
             rt.template_code,
             rt.template_name,
             rt.template_content,
+            rt.effect_node,
             rt.enabled,
             rc.config_id,
             rc.field_name,
@@ -213,6 +222,7 @@
             rt.template_code,
             rt.template_name,
             rt.template_content,
+            rt.effect_node,
             rt.enabled,
             rc.config_id,
             rc.field_name,
@@ -227,6 +237,27 @@
         WHERE
             rt.template_code = #{riskCode}
     </select>
+    <select id="getByNode" resultMap="RiskTemplateViewMap">
+        SELECT
+            rt.template_id,
+            rt.template_code,
+            rt.template_name,
+            rt.template_content,
+            rt.enabled,
+            rc.config_id,
+            rt.effect_node,
+            rc.field_name,
+            rc.field_desc,
+            rc.config_type,
+            rc.config_val,
+            rc.multy
+        FROM
+            yt_risk_template rt
+                LEFT JOIN yt_risk_template_config rtc ON rt.template_id = rtc.template_id
+                LEFT JOIN yt_risk_config rc ON rtc.config_id = rc.config_id
+        WHERE
+            rt.effect_node = #{effectNode}
+    </select>
     <select id="getApplicationOwner" resultType="java.lang.String">
         select
             user_id