Browse Source

导出数据添加字段

yuhongqi 2 tuần trước cách đây
mục cha
commit
69c799b987

+ 14 - 0
fs-admin/src/main/java/com/fs/task/LiveTask.java

@@ -83,6 +83,8 @@ public class LiveTask {
     public FsJstAftersalePushService fsJstAftersalePushService;
     @Autowired
     public RedisBatchHandler redisBatchHandler;
+    @Autowired
+    private IFsUserService fsUserService;
 
     // 订单银行回调数据丢失补偿
     public void recoveryBankOrder() {
@@ -206,6 +208,18 @@ public class LiveTask {
     }
 
 
+    /**
+     * 同步近三天直播订单关联的APP用户数据
+     */
+    @QuartzRunnable(name = "同步APP用户数据")
+    public void syncAppUser() {
+        try {
+            fsUserService.syncAppUsersForRecentLiveOrders(3);
+        } catch (Exception e) {
+            logger.error("同步APP用户数据失败", e);
+        }
+    }
+
         /**
          * 退款自动处理 24小时未审核自动审核通过 每小时执行一次
          */

+ 8 - 0
fs-service-system/src/main/java/com/fs/live/domain/LiveOrder.java

@@ -28,6 +28,14 @@ public class LiveOrder extends BaseEntity {
     @Excel(name = "会员ID")
     private String userId;
 
+    /** APP(his_java)用户ID */
+    @Excel(name = "APP用户ID")
+    private Long appUserId;
+
+    /** APP(his_java)部门名称 */
+    @Excel(name = "APP部门名称")
+    private String appDeptName;
+
     /** 管易云订单号 */
     @Excel(name = "管易云订单号")
     private String extendOrderId;

+ 3 - 0
fs-service-system/src/main/java/com/fs/live/mapper/LiveOrderMapper.java

@@ -146,4 +146,7 @@ public interface LiveOrderMapper {
      * */
     @Select("SELECT * FROM live_order WHERE create_time >= DATE_SUB(NOW(), INTERVAL 25 MINUTE) AND create_time <= DATE_SUB(NOW(), INTERVAL 4 MINUTE) AND status = 1 AND (refund_status IS NULL OR refund_status = '' OR refund_status = '0')")
     List<LiveOrder> selectBankOrder();
+
+    @Select("SELECT DISTINCT user_id FROM live_order WHERE create_time >= DATE_SUB(NOW(), INTERVAL #{days} DAY) AND user_id IS NOT NULL AND user_id != ''")
+    List<String> selectDistinctUserIdsFromRecentOrders(@Param("days") int days);
 }

+ 49 - 0
fs-service-system/src/main/java/com/fs/live/utils/CrossServiceMd5Util.java

@@ -0,0 +1,49 @@
+package com.fs.live.utils;
+
+import lombok.extern.slf4j.Slf4j;
+
+import javax.crypto.Cipher;
+import javax.crypto.spec.SecretKeySpec;
+import java.nio.charset.StandardCharsets;
+import java.security.MessageDigest;
+import java.util.Base64;
+
+/**
+ * 跨服务用户同步数据加解密(MD5派生密钥 + AES,无RSA长度限制)
+ */
+@Slf4j
+public class CrossServiceMd5Util {
+
+    private static final String SECRET = "ffhx_his_user_sync_2026";
+
+    public static String encrypt(String plainText) {
+        try {
+            SecretKeySpec keySpec = buildAesKey();
+            Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
+            cipher.init(Cipher.ENCRYPT_MODE, keySpec);
+            byte[] encrypted = cipher.doFinal(plainText.getBytes(StandardCharsets.UTF_8));
+            return Base64.getEncoder().encodeToString(encrypted);
+        } catch (Exception e) {
+            log.error("MD5加密失败", e);
+            throw new RuntimeException("MD5加密失败: " + e.getMessage(), e);
+        }
+    }
+
+    public static String decrypt(String cipherText) {
+        try {
+            SecretKeySpec keySpec = buildAesKey();
+            Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
+            cipher.init(Cipher.DECRYPT_MODE, keySpec);
+            byte[] decrypted = cipher.doFinal(Base64.getDecoder().decode(cipherText));
+            return new String(decrypted, StandardCharsets.UTF_8);
+        } catch (Exception e) {
+            log.error("MD5解密失败", e);
+            throw new RuntimeException("MD5解密失败: " + e.getMessage(), e);
+        }
+    }
+
+    private static SecretKeySpec buildAesKey() throws Exception {
+        byte[] key = MessageDigest.getInstance("MD5").digest(SECRET.getBytes(StandardCharsets.UTF_8));
+        return new SecretKeySpec(key, "AES");
+    }
+}

+ 3 - 21
fs-service-system/src/main/java/com/fs/live/utils/CrossServiceRsaUtil.java

@@ -1,11 +1,11 @@
 package com.fs.live.utils;
 
 import lombok.extern.slf4j.Slf4j;
-import org.springframework.beans.factory.annotation.Value;
 import org.springframework.stereotype.Component;
 
 import javax.annotation.PostConstruct;
 import javax.crypto.Cipher;
+import java.nio.charset.StandardCharsets;
 import java.security.KeyFactory;
 import java.security.PrivateKey;
 import java.security.PublicKey;
@@ -16,19 +16,11 @@ import java.util.Base64;
 /**
  * 跨服务RSA加密通信工具类
  * 用于rt_fhyx_java与his_java之间的安全通信
- *
- * 请求方向: rt_fhyx_java -> his_java
- *   rt_fhyx_java用his_java公钥(PUB_A)加密请求参数,his_java用私钥(PRI_A)解密验证
- *
- * 响应方向: his_java -> rt_fhyx_java
- *   his_java用rt_fhyx_java公钥(PUB_B)加密响应数据,rt_fhyx_java用私钥(PRI_B)解密
  */
 @Slf4j
 @Component
 public class CrossServiceRsaUtil {
 
-
-
     private static String staticHisPublicKey;
     private static String staticSelfPrivateKey;
 
@@ -41,18 +33,13 @@ public class CrossServiceRsaUtil {
                 staticSelfPrivateKey.length());
     }
 
-    /**
-     * 使用his_java公钥加密请求参数
-     * @param plainText 明文
-     * @return Base64编码的密文
-     */
     public static String encryptForRequest(String plainText) {
         try {
             PublicKey publicKey = KeyFactory.getInstance("RSA")
                     .generatePublic(new X509EncodedKeySpec(Base64.getDecoder().decode(staticHisPublicKey)));
             Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
             cipher.init(Cipher.ENCRYPT_MODE, publicKey);
-            byte[] encryptedBytes = cipher.doFinal(plainText.getBytes("UTF-8"));
+            byte[] encryptedBytes = cipher.doFinal(plainText.getBytes(StandardCharsets.UTF_8));
             return Base64.getEncoder().encodeToString(encryptedBytes);
         } catch (Exception e) {
             log.error("RSA请求加密失败", e);
@@ -60,11 +47,6 @@ public class CrossServiceRsaUtil {
         }
     }
 
-    /**
-     * 使用自身私钥解密响应数据
-     * @param encryptedText Base64编码的密文
-     * @return 解密后的明文
-     */
     public static String decryptResponse(String encryptedText) {
         try {
             PrivateKey privateKey = KeyFactory.getInstance("RSA")
@@ -72,7 +54,7 @@ public class CrossServiceRsaUtil {
             Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
             cipher.init(Cipher.DECRYPT_MODE, privateKey);
             byte[] decryptedBytes = cipher.doFinal(Base64.getDecoder().decode(encryptedText));
-            return new String(decryptedBytes, "UTF-8");
+            return new String(decryptedBytes, StandardCharsets.UTF_8);
         } catch (Exception e) {
             log.error("RSA响应解密失败", e);
             throw new RuntimeException("RSA响应解密失败: " + e.getMessage(), e);

+ 9 - 0
fs-service-system/src/main/java/com/fs/store/domain/FsUser.java

@@ -128,6 +128,15 @@ public class FsUser extends BaseEntity
 
     private String userCode;
 
+    /** APP(his_java)用户ID */
+    private Long appUserId;
+
+    /** APP(his_java)部门名称 */
+    private String appDeptName;
+
+    /** APP用户同步标记:0/空未同步,1已同步 */
+    private Integer appSyncFlag;
+
     public void setNickname(String nickname)
     {
         if(StringUtils.isNotEmpty(nickname)){

+ 2 - 2
fs-service-system/src/main/java/com/fs/store/mapper/FsStoreOrderMapper.java

@@ -182,7 +182,7 @@ public interface FsStoreOrderMapper
     List<FsStoreOrderVO> selectFsStoreOrderAllListVO(@Param("maps")FsStoreOrderParam param);
 
     @Select({"<script> " +
-            "select o.*,u.phone,u.register_code,u.register_date,u.source, c.company_name ,cu.nick_name as company_user_nick_name ,cu.phonenumber as company_usere_phonenumber ,fp.pay_money as lastPayMoney   from fs_store_order o left join fs_user u on o.user_id=u.user_id  left join company c on c.company_id=o.company_id left join company_user cu on cu.user_id=o.company_user_id  " +
+            "select o.*,u.phone,u.register_code,u.register_date,u.source,u.app_user_id as appUserId,u.app_dept_name as appDeptName, c.company_name ,cu.nick_name as company_user_nick_name ,cu.phonenumber as company_usere_phonenumber ,fp.pay_money as lastPayMoney   from fs_store_order o left join fs_user u on o.user_id=u.user_id  left join company c on c.company_id=o.company_id left join company_user cu on cu.user_id=o.company_user_id  " +
             " left join (select * from fs_store_payment where is_pay_remain = 1 and status = 1) fp on o.id = fp.order_id  "+
             "<if test = 'maps.productName != null and  maps.productName !=  \"\" '> " +
             "left join fs_store_order_item oi on o.id = oi.order_id "+
@@ -550,7 +550,7 @@ public interface FsStoreOrderMapper
     List<FsStoreOrder> selectFsStoreOrderListByFinish7Day();
 
     @Select({"<script> " +
-            "select o.*,deliver.deliver_id as deliver_code,deliver.state_ex as delivery_type_code,deliver.status as kdn_delivery_status,cts.name as scheduleName,u.nickname,u.phone,cc.push_code,cc.create_time as customer_create_time,cc.source,cc.customer_code, c.company_name ,cu.nick_name as company_user_nick_name ,cu.phonenumber as company_usere_phonenumber ,p.title as package_title ,p.cate_id,CASE WHEN o.certificates IS NULL OR o.certificates = '' THEN 0 ELSE 1 END AS is_upload  " +
+            "select o.*,deliver.deliver_id as deliver_code,deliver.state_ex as delivery_type_code,deliver.status as kdn_delivery_status,cts.name as scheduleName,u.nickname,u.phone,u.app_user_id as appUserId,u.app_dept_name as appDeptName,cc.push_code,cc.create_time as customer_create_time,cc.source,cc.customer_code, c.company_name ,cu.nick_name as company_user_nick_name ,cu.phonenumber as company_usere_phonenumber ,p.title as package_title ,p.cate_id,CASE WHEN o.certificates IS NULL OR o.certificates = '' THEN 0 ELSE 1 END AS is_upload  " +
             " from fs_store_order o  left JOIN fs_store_product_package p on o.package_id=p.package_id left join fs_user u on o.user_id=u.user_id  " +
             " left join company c on c.company_id=o.company_id left join company_user cu on cu.user_id=o.company_user_id left join crm_customer cc on cc.customer_id=o.customer_id left join company_tcm_schedule cts on cts.id = o.schedule_id " +
             " left join fs_store_delivers deliver on deliver.order_id=o.id "+

+ 15 - 0
fs-service-system/src/main/java/com/fs/store/mapper/FsUserMapper.java

@@ -233,4 +233,19 @@ public interface FsUserMapper
      * @return List<Map> 每个Map包含user_id和status字段
      */
     List<FsUser> selectUserStatusByIds(@Param("userIds") List<Long> userIds);
+
+    @Select({"<script>",
+            "SELECT user_id, union_id, app_user_id, app_dept_name, app_sync_flag",
+            " FROM fs_user",
+            " WHERE (app_sync_flag IS NULL OR app_sync_flag = 0)",
+            " AND ma_open_id IS NOT NULL AND ma_open_id != ''",
+            " AND user_id IN",
+            " <foreach collection='userIds' item='id' open='(' separator=',' close=')'>#{id}</foreach>",
+            "</script>"})
+    List<FsUser> selectUnsyncedAppUsersByUserIds(@Param("userIds") List<Long> userIds);
+
+    /**
+     * 批量更新 APP 用户同步字段
+     */
+    int batchUpdateAppSyncFields(@Param("list") List<FsUser> list);
 }

+ 5 - 0
fs-service-system/src/main/java/com/fs/store/service/IFsUserService.java

@@ -142,4 +142,9 @@ public interface IFsUserService
     FsUser selectWsFsUserById(long userId);
 
     R loginByApp(FsUser param);
+
+    /**
+     * 同步近N天直播订单关联且未同步的APP用户数据
+     */
+    void syncAppUsersForRecentLiveOrders(int days);
 }

+ 126 - 0
fs-service-system/src/main/java/com/fs/store/service/impl/FsUserServiceImpl.java

@@ -14,12 +14,16 @@ import cn.hutool.core.date.DateTime;
 import cn.hutool.core.util.ObjectUtil;
 import cn.hutool.json.JSONUtil;
 import com.alibaba.fastjson.JSONObject;
+import com.alibaba.fastjson.TypeReference;
 import com.fs.common.core.domain.R;
 import com.fs.common.utils.DateUtils;
 import com.fs.common.utils.IpUtil;
 import com.fs.live.domain.LiveOrder;
 import com.fs.live.domain.LiveOrderItem;
+import com.fs.live.mapper.LiveOrderMapper;
 import com.fs.live.domain.LiveRewardCompensation;
+import com.fs.live.utils.CrossServiceMd5Util;
+import com.fs.live.utils.CrossServiceRsaUtil;
 import com.fs.live.mapper.LiveRewardCompensationMapper;
 import com.fs.live.utils.CryptoUtil;
 import com.fs.live.vo.HisFsUserVO;
@@ -37,6 +41,7 @@ import com.fs.store.vo.FsUserVO;
 import com.fs.store.vo.FsCompanyUserListQueryVO;
 import com.fs.store.vo.FsUserShareVO;
 import com.fs.store.vo.FsUserTuiVO;
+import com.fs.store.vo.AppUserSyncVo;
 import com.fs.wx.miniapp.config.WxMaConfiguration;
 import com.fs.wx.miniapp.config.WxMaProperties;
 import me.chanjar.weixin.common.error.WxErrorException;
@@ -55,9 +60,13 @@ import org.springframework.aop.framework.AopContext;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.context.annotation.EnableAspectJAutoProxy;
 import org.springframework.scheduling.annotation.Async;
+import org.springframework.http.HttpEntity;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.MediaType;
 import org.springframework.stereotype.Service;
 import com.fs.store.mapper.FsUserMapper;
 import com.fs.store.service.IFsUserService;
+import org.springframework.web.client.RestTemplate;
 import org.springframework.transaction.annotation.Transactional;
 
 import javax.annotation.PostConstruct;
@@ -78,6 +87,9 @@ public class FsUserServiceImpl implements IFsUserService
     @Autowired
     private FsUserMapper fsUserMapper;
 
+    @Autowired
+    private LiveOrderMapper liveOrderMapper;
+
     @Autowired
     private FsStoreProductAttrValueMapper storeProductAttrValueMapper;
 
@@ -731,4 +743,118 @@ public class FsUserServiceImpl implements IFsUserService
 //        }
 //    }
 
+    private static final String APP_USER_SYNC_URL = "http://42.194.245.189:8010/app/common/syncAppUsers";
+    private static final int APP_USER_SYNC_BATCH_SIZE = 50;
+
+    @Override
+    public void syncAppUsersForRecentLiveOrders(int days) {
+        List<String> userIdStrList = liveOrderMapper.selectDistinctUserIdsFromRecentOrders(days);
+        if (userIdStrList == null || userIdStrList.isEmpty()) {
+            return;
+        }
+        List<Long> userIds = userIdStrList.stream()
+                .filter(StringUtils::isNotBlank)
+                .map(id -> {
+                    try {
+                        return Long.parseLong(id.trim());
+                    } catch (NumberFormatException e) {
+                        return null;
+                    }
+                })
+                .filter(Objects::nonNull)
+                .distinct()
+                .collect(Collectors.toList());
+        if (userIds.isEmpty()) {
+            return;
+        }
+
+        List<FsUser> usersToSync = fsUserMapper.selectUnsyncedAppUsersByUserIds(userIds);
+
+        if (usersToSync == null || usersToSync.isEmpty()) {
+            return;
+        }
+
+        List<String> unionIds = usersToSync.stream()
+                .map(FsUser::getUnionId)
+                .filter(StringUtils::isNotBlank)
+                .distinct()
+                .collect(Collectors.toList());
+        if (unionIds.isEmpty()) {
+            return;
+        }
+
+        Map<String, FsUser> userByUnionId = usersToSync.stream()
+                .filter(u -> StringUtils.isNotBlank(u.getUnionId()))
+                .collect(Collectors.toMap(FsUser::getUnionId, Function.identity(), (a, b) -> a));
+
+        for (int i = 0; i < unionIds.size(); i += APP_USER_SYNC_BATCH_SIZE) {
+            int end = Math.min(i + APP_USER_SYNC_BATCH_SIZE, unionIds.size());
+            List<String> batch = unionIds.subList(i, end);
+            try {
+                Map<String, AppUserSyncVo> syncMap = requestAppUserSync(batch);
+                if (syncMap == null || syncMap.isEmpty()) {
+                    continue;
+                }
+                List<FsUser> updateList = new ArrayList<>();
+                for (Map.Entry<String, AppUserSyncVo> entry : syncMap.entrySet()) {
+                    AppUserSyncVo vo = entry.getValue();
+                    if (vo == null || vo.getAppUserId() == null) {
+                        continue;
+                    }
+                    FsUser localUser = userByUnionId.get(entry.getKey());
+                    if (localUser == null) {
+                        continue;
+                    }
+                    FsUser update = new FsUser();
+                    update.setUserId(localUser.getUserId());
+                    update.setAppUserId(vo.getAppUserId());
+                    update.setAppDeptName(vo.getAppDeptName());
+                    update.setAppSyncFlag(1);
+                    updateList.add(update);
+                }
+                batchUpdateAppSyncUsers(updateList);
+            } catch (Exception e) {
+                logger.error("批量同步APP用户失败, batchSize={}", batch.size(), e);
+            }
+        }
+    }
+
+    private void batchUpdateAppSyncUsers(List<FsUser> updateList) {
+        if (updateList == null || updateList.isEmpty()) {
+            return;
+        }
+        fsUserMapper.batchUpdateAppSyncFields(updateList);
+    }
+
+    private Map<String, AppUserSyncVo> requestAppUserSync(List<String> maOpenIds) {
+        String encryptedStr = CrossServiceRsaUtil.encryptForRequest("user" + System.currentTimeMillis());
+        Map<String, Object> paramMap = new HashMap<>();
+        paramMap.put("encryptedStr", encryptedStr);
+        paramMap.put("unionIds", maOpenIds);
+
+        RestTemplate restTemplate = new RestTemplate();
+        HttpHeaders headers = new HttpHeaders();
+        headers.setContentType(MediaType.APPLICATION_JSON);
+        HttpEntity<Map<String, Object>> requestEntity = new HttpEntity<>(paramMap, headers);
+        String responseBody = restTemplate.postForObject(APP_USER_SYNC_URL, requestEntity, String.class);
+        if (StringUtils.isBlank(responseBody)) {
+            logger.warn("同步APP用户接口无响应");
+            return Collections.emptyMap();
+        }
+        JSONObject respObj = JSONObject.parseObject(responseBody);
+        if (respObj == null || !"200".equals(respObj.getString("code"))) {
+            logger.warn("同步APP用户接口失败: {}", responseBody);
+            return Collections.emptyMap();
+        }
+        String encryptedData = respObj.getString("data");
+        if (StringUtils.isBlank(encryptedData)) {
+            return Collections.emptyMap();
+        }
+        String decryptedJson = CrossServiceMd5Util.decrypt(encryptedData);
+        if (StringUtils.isBlank(decryptedJson)) {
+            return Collections.emptyMap();
+        }
+        return JSONObject.parseObject(decryptedJson, new TypeReference<Map<String, AppUserSyncVo>>() {});
+    }
+
 }

+ 13 - 0
fs-service-system/src/main/java/com/fs/store/vo/AppUserSyncVo.java

@@ -0,0 +1,13 @@
+package com.fs.store.vo;
+
+import lombok.Data;
+
+/**
+ * his_java 跨服务用户同步返回实体
+ */
+@Data
+public class AppUserSyncVo {
+    private Long appUserId;
+    private String unionId;
+    private String appDeptName;
+}

+ 7 - 0
fs-service-system/src/main/java/com/fs/store/vo/FsStoreOrderExportVO.java

@@ -28,6 +28,13 @@ public class FsStoreOrderExportVO implements Serializable
     private String orderCode;
     @Excel(name = "会员ID")
     private Long userId;
+
+    @Excel(name = "APP用户ID")
+    private Long appUserId;
+
+    @Excel(name = "APP部门名称")
+    private String appDeptName;
+
     /** 额外订单号 */
     @Excel(name = "管易云订单号")
     private String extendOrderId;

+ 7 - 0
fs-service-system/src/main/java/com/fs/store/vo/FsStoreOrderVO.java

@@ -28,6 +28,13 @@ public class FsStoreOrderVO implements Serializable
     private String orderCode;
     @Excel(name = "会员ID")
     private Long userId;
+
+    @Excel(name = "APP用户ID")
+    private Long appUserId;
+
+    @Excel(name = "APP部门名称")
+    private String appDeptName;
+
     /** 额外订单号 */
     @Excel(name = "管易云订单号")
     private String extendOrderId;

+ 4 - 0
fs-service-system/src/main/resources/mapper/live/LiveOrderMapper.xml

@@ -76,6 +76,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         <result property="productIntroduce"    column="product_introduce"    />
         <result property="companyUserName"    column="company_user_name"    />
         <result property="companyName"    column="company_name"    />
+        <result property="appUserId"    column="app_user_id"    />
+        <result property="appDeptName"    column="app_dept_name"    />
         <result property="customerId"    column="customer_id"    />
     </resultMap>
 
@@ -109,11 +111,13 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         a.delivery_pay_money,	a.delivery_import_time,	a.delivery_send_time,	a.is_after_sales,	a.dept_id,
         a.channel,	a.source,	a.bill_price,	a.total_postage,	a.pay_postage,	a.gain_integral,
         a.use_integral,	a.pay_integral,	a.back_integral,	a.is_edit_money,	b.product_info as product_introduce,a.customer_id,
+        fu.app_user_id, fu.app_dept_name,
         b.product_name as product_name,'0' as delivery_collection_amount
         FROM
         live_order a LEFT JOIN fs_store_product b ON a.product_id = b.product_id
         left join company_user cu on a.company_user_id = cu.user_id
         left join company c on a.company_id = c.company_id
+        left join fs_user fu on a.user_id = fu.user_id
         <where>
             <if test="liveId != null "> and live_id = #{liveId}</if>
             <if test="storeId != null "> and store_id = #{storeId}</if>

+ 2 - 1
fs-service-system/src/main/resources/mapper/store/FsStoreOrderMapper.xml

@@ -560,7 +560,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         o.`order_visit`,
         o.`service_fee`
                ,u.phone,u.register_code,u.register_date,
-               u.source, c.company_name ,
+               u.source, u.app_user_id as appUserId, u.app_dept_name as appDeptName,
+               c.company_name ,
                cu.nick_name as company_user_nick_name ,
                cu.phonenumber as company_usere_phonenumber
                 from fs_store_order o

+ 17 - 1
fs-service-system/src/main/resources/mapper/store/FsUserMapper.xml

@@ -41,10 +41,13 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         <result property="registerCode"    column="register_code"    />
         <result property="source"    column="source"    />
         <result property="userCode"    column="user_code"    />
+        <result property="appUserId"    column="app_user_id"    />
+        <result property="appDeptName"    column="app_dept_name"    />
+        <result property="appSyncFlag"    column="app_sync_flag"    />
     </resultMap>
 
     <sql id="selectFsUserVo">
-        select user_id, username, password, real_name, birthday, id_card, remark, nickname, avatar, phone, create_time, update_time, last_ip, now_money, brokerage_price, integral, sign_num, status, level, spread_user_id, spread_time, user_type, is_promoter, pay_count, spread_count, addres,ma_open_id,mp_open_id,union_id, is_del,is_weixin_auth,company_id,company_user_id,register_date,register_code,source,user_code from fs_user
+        select user_id, username, password, real_name, birthday, id_card, remark, nickname, avatar, phone, create_time, update_time, last_ip, now_money, brokerage_price, integral, sign_num, status, level, spread_user_id, spread_time, user_type, is_promoter, pay_count, spread_count, addres,ma_open_id,mp_open_id,union_id, is_del,is_weixin_auth,company_id,company_user_id,register_date,register_code,source,user_code,app_user_id,app_dept_name,app_sync_flag from fs_user
     </sql>
 
     <select id="selectFsUserList" parameterType="FsUser" resultMap="FsUserResult">
@@ -228,10 +231,23 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="registerCode != null">register_code = #{registerCode},</if>
             <if test="source != null">source = #{source},</if>
             <if test="userCode != null">user_code = #{userCode},</if>
+            <if test="appUserId != null">app_user_id = #{appUserId},</if>
+            <if test="appDeptName != null">app_dept_name = #{appDeptName},</if>
+            <if test="appSyncFlag != null">app_sync_flag = #{appSyncFlag},</if>
         </trim>
         where user_id = #{userId}
     </update>
 
+    <update id="batchUpdateAppSyncFields" parameterType="java.util.List">
+        <foreach collection="list" item="item" separator=";">
+            update fs_user
+            set app_user_id = #{item.appUserId},
+                app_dept_name = #{item.appDeptName},
+                app_sync_flag = #{item.appSyncFlag}
+            where user_id = #{item.userId}
+        </foreach>
+    </update>
+
     <delete id="deleteFsUserById" parameterType="Long">
         delete from fs_user where user_id = #{userId}
     </delete>