diff --git a/lib/EmergencyService/zdxtEmergencyBootStart/src/main/resources/templates/project/user/view.html b/lib/EmergencyService/zdxtEmergencyBootStart/src/main/resources/templates/project/user/view.html
new file mode 100644
index 00000000..9015d727
--- /dev/null
+++ b/lib/EmergencyService/zdxtEmergencyBootStart/src/main/resources/templates/project/user/view.html
@@ -0,0 +1,99 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/lib/EmergencyService/zdxtEmergencyKnowledgeService/src/main/java/com/zdxt/knowledge/controller/WechatLoginController.java b/lib/EmergencyService/zdxtEmergencyKnowledgeService/src/main/java/com/zdxt/knowledge/controller/WechatLoginController.java
new file mode 100755
index 00000000..64893759
--- /dev/null
+++ b/lib/EmergencyService/zdxtEmergencyKnowledgeService/src/main/java/com/zdxt/knowledge/controller/WechatLoginController.java
@@ -0,0 +1,63 @@
+package com.zdxt.knowledge.controller;
+
+import com.zdxt.common.controller.BaseController;
+import com.zdxt.common.util.TableDataInfo;
+import com.zdxt.knowledge.domain.WechatLoginRequest;
+import com.zdxt.knowledge.domain.WechatUser;
+import com.zdxt.knowledge.service.WechatService;
+import io.swagger.annotations.ApiOperation;
+import org.apache.shiro.authz.annotation.RequiresPermissions;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Controller;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+import java.util.Map;
+
+
+/**
+ * 【请填写功能名称】Controller
+ *
+ * @author ruoyi
+ * @date 2023-01-23
+ */
+@Controller
+@RequestMapping("/small/wechat")
+public class WechatLoginController extends BaseController {
+
+ @Autowired
+ private WechatService wechatService;
+
+ private String prefix = "project/user";
+
+ @RequiresPermissions("project:user:view")
+ @GetMapping("/view")
+ public String view() {
+ return prefix + "/view";
+ }
+
+
+ /**
+ * 查询
+ */
+ @RequiresPermissions("project:user:list")
+ @PostMapping("/list")
+ @ResponseBody
+ public TableDataInfo list(WechatUser wechatUser) {
+ startPage();
+ List list = wechatService.selectList(wechatUser);
+ return getDataTable(list);
+ }
+
+ /**
+ * 登入
+ */
+ @ApiOperation(value = "登入接口", httpMethod = "POST")
+ @PostMapping("/login")
+ @ResponseBody
+ public Map login(@Validated @RequestBody WechatLoginRequest loginRequest) throws Exception {
+ Map userInfoMap = wechatService.getUserInfoMap(loginRequest);
+ return userInfoMap;
+ }
+}
diff --git a/lib/EmergencyService/zdxtEmergencyKnowledgeService/src/main/java/com/zdxt/knowledge/domain/RawDataDO.java b/lib/EmergencyService/zdxtEmergencyKnowledgeService/src/main/java/com/zdxt/knowledge/domain/RawDataDO.java
new file mode 100644
index 00000000..2af3e042
--- /dev/null
+++ b/lib/EmergencyService/zdxtEmergencyKnowledgeService/src/main/java/com/zdxt/knowledge/domain/RawDataDO.java
@@ -0,0 +1,60 @@
+package com.zdxt.knowledge.domain;
+
+import java.io.Serializable;
+
+public class RawDataDO implements Serializable {
+ private String nickName;
+ private String avatarUrl;
+ private Integer gender;
+ private String city;
+ private String country;
+ private String province;
+
+ public String getNickName() {
+ return nickName;
+ }
+
+ public void setNickName(String nickName) {
+ this.nickName = nickName;
+ }
+
+ public String getAvatarUrl() {
+ return avatarUrl;
+ }
+
+ public void setAvatarUrl(String avatarUrl) {
+ this.avatarUrl = avatarUrl;
+ }
+
+ public Integer getGender() {
+ return gender;
+ }
+
+ public void setGender(Integer gender) {
+ this.gender = gender;
+ }
+
+ public String getCity() {
+ return city;
+ }
+
+ public void setCity(String city) {
+ this.city = city;
+ }
+
+ public String getCountry() {
+ return country;
+ }
+
+ public void setCountry(String country) {
+ this.country = country;
+ }
+
+ public String getProvince() {
+ return province;
+ }
+
+ public void setProvince(String province) {
+ this.province = province;
+ }
+}
diff --git a/lib/EmergencyService/zdxtEmergencyKnowledgeService/src/main/java/com/zdxt/knowledge/domain/WechatLoginRequest.java b/lib/EmergencyService/zdxtEmergencyKnowledgeService/src/main/java/com/zdxt/knowledge/domain/WechatLoginRequest.java
new file mode 100644
index 00000000..ba6abfa5
--- /dev/null
+++ b/lib/EmergencyService/zdxtEmergencyKnowledgeService/src/main/java/com/zdxt/knowledge/domain/WechatLoginRequest.java
@@ -0,0 +1,63 @@
+package com.zdxt.knowledge.domain;
+
+import io.swagger.annotations.ApiModelProperty;
+
+import java.io.Serializable;
+
+public class WechatLoginRequest implements Serializable {
+
+ @ApiModelProperty(value = "微信code", required = true)
+ private String code;
+
+ @ApiModelProperty(value = "用户非敏感字段")
+ private String rawData;
+
+ @ApiModelProperty(value = "签名")
+ private String signature;
+
+ @ApiModelProperty(value = "用户敏感字段")
+ private String encryptedData;
+
+ @ApiModelProperty(value = "解密向量")
+ private String iv;
+
+ public String getCode() {
+ return code;
+ }
+
+ public void setCode(String code) {
+ this.code = code;
+ }
+
+ public String getRawData() {
+ return rawData;
+ }
+
+ public void setRawData(String rawData) {
+ this.rawData = rawData;
+ }
+
+ public String getSignature() {
+ return signature;
+ }
+
+ public void setSignature(String signature) {
+ this.signature = signature;
+ }
+
+ public String getEncryptedData() {
+ return encryptedData;
+ }
+
+ public void setEncryptedData(String encryptedData) {
+ this.encryptedData = encryptedData;
+ }
+
+ public String getIv() {
+ return iv;
+ }
+
+ public void setIv(String iv) {
+ this.iv = iv;
+ }
+}
diff --git a/lib/EmergencyService/zdxtEmergencyKnowledgeService/src/main/java/com/zdxt/knowledge/domain/WechatUser.java b/lib/EmergencyService/zdxtEmergencyKnowledgeService/src/main/java/com/zdxt/knowledge/domain/WechatUser.java
new file mode 100644
index 00000000..7f0ca7b9
--- /dev/null
+++ b/lib/EmergencyService/zdxtEmergencyKnowledgeService/src/main/java/com/zdxt/knowledge/domain/WechatUser.java
@@ -0,0 +1,138 @@
+package com.zdxt.knowledge.domain;
+
+import java.io.Serializable;
+import java.util.Date;
+
+public class WechatUser implements Serializable {
+ private static final long serialVersionUID = 1L;
+
+ private String guid;
+
+ private String token;
+
+ private String nickname;
+
+ private String avatarUrl;
+
+ private Integer gender;
+
+ private String country;
+
+ private String province;
+
+ private String city;
+
+ private String mobile;
+
+ private String openId;
+
+ private String unionId;
+
+ private String creator;
+
+ private Date createTime;
+
+ public String getGuid() {
+ return guid;
+ }
+
+ public void setGuid(String guid) {
+ this.guid = guid;
+ }
+
+ public String getToken() {
+ return token;
+ }
+
+ public void setToken(String token) {
+ this.token = token;
+ }
+
+ public String getNickname() {
+ return nickname;
+ }
+
+ public void setNickname(String nickname) {
+ this.nickname = nickname;
+ }
+
+ public String getAvatarUrl() {
+ return avatarUrl;
+ }
+
+ public void setAvatarUrl(String avatarUrl) {
+ this.avatarUrl = avatarUrl;
+ }
+
+ public Integer getGender() {
+ return gender;
+ }
+
+ public void setGender(Integer gender) {
+ this.gender = gender;
+ }
+
+ public String getCountry() {
+ return country;
+ }
+
+ public void setCountry(String country) {
+ this.country = country;
+ }
+
+ public String getProvince() {
+ return province;
+ }
+
+ public void setProvince(String province) {
+ this.province = province;
+ }
+
+ public String getCity() {
+ return city;
+ }
+
+ public void setCity(String city) {
+ this.city = city;
+ }
+
+ public String getMobile() {
+ return mobile;
+ }
+
+ public void setMobile(String mobile) {
+ this.mobile = mobile;
+ }
+
+ public String getOpenId() {
+ return openId;
+ }
+
+ public void setOpenId(String openId) {
+ this.openId = openId;
+ }
+
+ public String getUnionId() {
+ return unionId;
+ }
+
+ public void setUnionId(String unionId) {
+ this.unionId = unionId;
+ }
+
+ public String getCreator() {
+ return creator;
+ }
+
+ public void setCreator(String creator) {
+ this.creator = creator;
+ }
+
+ public Date getCreateTime() {
+ return createTime;
+ }
+
+ public void setCreateTime(Date createTime) {
+ this.createTime = createTime;
+ }
+}
diff --git a/lib/EmergencyService/zdxtEmergencyKnowledgeService/src/main/java/com/zdxt/knowledge/mapper/WechatLoginMapper.java b/lib/EmergencyService/zdxtEmergencyKnowledgeService/src/main/java/com/zdxt/knowledge/mapper/WechatLoginMapper.java
new file mode 100644
index 00000000..f556798f
--- /dev/null
+++ b/lib/EmergencyService/zdxtEmergencyKnowledgeService/src/main/java/com/zdxt/knowledge/mapper/WechatLoginMapper.java
@@ -0,0 +1,23 @@
+package com.zdxt.knowledge.mapper;
+
+import com.zdxt.knowledge.domain.WechatUser;
+
+import java.util.List;
+
+/**
+ * Mapper接口
+ *
+ * @author ruoyi
+ * @date 2023-12-26
+ */
+public interface WechatLoginMapper {
+
+ public List selectList(WechatUser wechatUser);
+
+ public WechatUser getByOpenId(String openId);
+
+ public int insertUser(WechatUser wechatUser);
+
+ public int updateByOpenId(WechatUser wechatUser);
+
+}
diff --git a/lib/EmergencyService/zdxtEmergencyKnowledgeService/src/main/java/com/zdxt/knowledge/service/WechatService.java b/lib/EmergencyService/zdxtEmergencyKnowledgeService/src/main/java/com/zdxt/knowledge/service/WechatService.java
new file mode 100644
index 00000000..c361d73a
--- /dev/null
+++ b/lib/EmergencyService/zdxtEmergencyKnowledgeService/src/main/java/com/zdxt/knowledge/service/WechatService.java
@@ -0,0 +1,27 @@
+package com.zdxt.knowledge.service;
+
+import com.zdxt.knowledge.domain.WechatLoginRequest;
+import com.zdxt.knowledge.domain.WechatUser;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Service接口
+ *
+ * @author ruoyi
+ * @date 2023-01-23
+ */
+public interface WechatService {
+
+ public List selectList(WechatUser wechatUser);
+
+ /**
+ * 登陆
+ *
+ * @param loginRequest
+ * @return 集合
+ */
+ Map getUserInfoMap(WechatLoginRequest loginRequest) throws Exception;
+
+}
diff --git a/lib/EmergencyService/zdxtEmergencyKnowledgeService/src/main/java/com/zdxt/knowledge/service/impl/WechatServiceImpl.java b/lib/EmergencyService/zdxtEmergencyKnowledgeService/src/main/java/com/zdxt/knowledge/service/impl/WechatServiceImpl.java
new file mode 100644
index 00000000..9961707c
--- /dev/null
+++ b/lib/EmergencyService/zdxtEmergencyKnowledgeService/src/main/java/com/zdxt/knowledge/service/impl/WechatServiceImpl.java
@@ -0,0 +1,198 @@
+package com.zdxt.knowledge.service.impl;
+
+import cn.hutool.core.lang.Assert;
+import cn.hutool.core.util.StrUtil;
+import cn.hutool.http.HttpRequest;
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONObject;
+import com.zdxt.common.util.StringUtils;
+import com.zdxt.knowledge.domain.RawDataDO;
+import com.zdxt.knowledge.domain.WechatLoginRequest;
+import com.zdxt.knowledge.domain.WechatUser;
+import com.zdxt.knowledge.mapper.WechatLoginMapper;
+import com.zdxt.knowledge.service.WechatService;
+import com.zdxt.knowledge.utils.HttpClientUtils;
+import lombok.extern.slf4j.Slf4j;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.util.encoders.Base64;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import javax.crypto.Cipher;
+import javax.crypto.spec.IvParameterSpec;
+import javax.crypto.spec.SecretKeySpec;
+import java.security.AlgorithmParameters;
+import java.security.Security;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Service业务层处理
+ *
+ * @author ruoyi
+ * @date 2023-01-23
+ */
+@Service
+@Slf4j
+public class WechatServiceImpl implements WechatService {
+
+ @Autowired
+ private WechatLoginMapper wechatLoginMapper;
+
+ private static final String REQUEST_URL = "https://api.weixin.qq.com/sns/jscode2session";
+ private static final String APPID = "wx93ec514a5c456507";
+ private static final String SECRET = "f5a3f295dbee3cc1c2f73b9a324c2eb2";
+ private static final String GRANT_TYPE = "authorization_code";
+
+
+ @Override
+ public List selectList(WechatUser wechatUser) {
+ return wechatLoginMapper.selectList(wechatUser);
+ }
+
+ @Override
+ public Map getUserInfoMap(WechatLoginRequest loginRequest) throws Exception {
+ Map userInfoMap = new HashMap<>();
+ log.info("Start get SessionKey,loginRequest的数据为:" + JSONObject.toJSONString(loginRequest));
+ JSONObject sessionKeyOpenId = getSessionKeyOrOpenId(loginRequest.getCode());
+ Assert.isTrue(sessionKeyOpenId != null, "获取sessionKeyOpenId出错");
+
+ // 获取openId && sessionKey
+ String openId = sessionKeyOpenId.getString("openid");
+ Assert.isTrue(openId != null, "获取openId出错");
+ String sessionKey = sessionKeyOpenId.getString("session_key");
+ WechatUser insertOrUpdateDO = buildWechatUserAuthInfoDO(loginRequest, openId, sessionKey);
+
+ // 根据code保存openId和sessionKey
+ JSONObject sessionObj = new JSONObject();
+ sessionObj.put("openId", openId);
+ sessionObj.put("sessionKey", sessionKey);
+ // 这里的set方法使用Redis,key自行替换,10表示10天
+// stringJedisClientTem.set(WechatRedisPrefixConstant.USER_OPPEN_ID_AND_SESSION_KEY_PREFIX + loginRequest.getCode(),
+// sessionObj.toJSONString(), 10, TimeUnit.DAYS);
+
+ // 根据openid查询用户
+ WechatUser user = wechatLoginMapper.getByOpenId(openId);
+
+ if (user == null) {
+ // 用户不存在,insert用户,这里加了个分布式锁,防止insert重复用户
+// if (setLock(WechatRedisPrefixConstant.INSERT_USER_DISTRIBUTED_LOCK_PREFIX + openId, "1", 10)) {
+// // 用户入库
+// insertOrUpdateDO.setToken(getToken());
+// wechatLoginMapper.insertUser(insertOrUpdateDO);
+// userInfoMap.put("token", insertOrUpdateDO.getToken());
+// }
+ insertOrUpdateDO.setGuid(StringUtils.getUUID());
+ insertOrUpdateDO.setCreator("SYSTEM");
+ insertOrUpdateDO.setToken(getToken(insertOrUpdateDO.getGuid(),insertOrUpdateDO.getNickname()));
+ wechatLoginMapper.insertUser(insertOrUpdateDO);
+ userInfoMap.put("token", insertOrUpdateDO.getToken());
+ } else {
+ userInfoMap.put("token", user.getToken());
+ // 已存在,做已存在的处理,如更新用户的头像,昵称等,根据openID更新
+ wechatLoginMapper.updateByOpenId(insertOrUpdateDO);
+ }
+
+ return userInfoMap;
+ }
+
+ private JSONObject getSessionKeyOrOpenId(String code) throws Exception {
+
+// String apiUrl = StrUtil.format("https://api.weixin.qq.com/sns/jscode2session?appid={}&secret={}&js_code={}&grant_type=authorization_code", APPID, SECRET, code);
+// String body = HttpRequest.get(apiUrl).execute().body();
+
+ Map requestUrlParam = new HashMap<>();
+ // 小程序appId
+ requestUrlParam.put("appid", APPID);
+ // 小程序secret
+ requestUrlParam.put("secret", SECRET);
+ // 小程序端返回的code
+ requestUrlParam.put("js_code", code);
+ // 默认参数
+ requestUrlParam.put("grant_type", GRANT_TYPE);
+
+ // 发送post请求读取调用微信接口获取openid用户唯一标识
+ String result = HttpClientUtils.doPost(REQUEST_URL, requestUrlParam);
+ return JSON.parseObject(result);
+ }
+
+ private WechatUser buildWechatUserAuthInfoDO(WechatLoginRequest loginRequest, String sessionKey, String openId) {
+ WechatUser wechatUserDO = new WechatUser();
+ wechatUserDO.setOpenId(openId);
+
+ if (loginRequest.getRawData() != null) {
+ RawDataDO rawDataDO = JSON.parseObject(loginRequest.getRawData(), RawDataDO.class);
+ wechatUserDO.setNickname(rawDataDO.getNickName());
+ wechatUserDO.setAvatarUrl(rawDataDO.getAvatarUrl());
+ wechatUserDO.setGender(rawDataDO.getGender());
+ wechatUserDO.setCity(rawDataDO.getCity());
+ wechatUserDO.setCountry(rawDataDO.getCountry());
+ wechatUserDO.setProvince(rawDataDO.getProvince());
+ }
+
+ // 解密加密信息,获取unionID
+ if (loginRequest.getEncryptedData() != null) {
+ JSONObject encryptedData = getEncryptedData(loginRequest.getEncryptedData(), sessionKey, loginRequest.getIv());
+ if (encryptedData != null) {
+ String unionId = encryptedData.getString("unionId");
+ String phone = encryptedData.getString("phoneNumber");
+ wechatUserDO.setUnionId(unionId);
+ }
+ }
+
+ return wechatUserDO;
+ }
+
+ private JSONObject getEncryptedData(String encryptedData, String sessionkey, String iv) {
+ // 被加密的数据
+ byte[] dataByte = Base64.decode(encryptedData);
+ // 加密秘钥
+ byte[] keyByte = Base64.decode(sessionkey);
+ // 偏移量
+ byte[] ivByte = Base64.decode(iv);
+ try {
+ // 如果密钥不足16位,那么就补足.这个if中的内容很重要
+ int base = 16;
+ if (keyByte.length % base != 0) {
+ int groups = keyByte.length / base + 1;
+ byte[] temp = new byte[groups * base];
+ Arrays.fill(temp, (byte) 0);
+ System.arraycopy(keyByte, 0, temp, 0, keyByte.length);
+ keyByte = temp;
+ }
+ // 初始化
+ Security.addProvider(new BouncyCastleProvider());
+ Cipher cipher = Cipher.getInstance("AES/CBC/PKCS7Padding", "BC");
+ SecretKeySpec spec = new SecretKeySpec(keyByte, "AES");
+ AlgorithmParameters parameters = AlgorithmParameters.getInstance("AES");
+ parameters.init(new IvParameterSpec(ivByte));
+ cipher.init(Cipher.DECRYPT_MODE, spec, parameters);// 初始化
+ byte[] resultByte = cipher.doFinal(dataByte);
+ if (null != resultByte && resultByte.length > 0) {
+ String result = new String(resultByte, "UTF-8");
+ return JSONObject.parseObject(result);
+ }
+ } catch (Exception e) {
+ log.error("解密加密信息报错", e.getMessage());
+ }
+ return null;
+ }
+
+// private boolean setLock(String key, String value, long expire) throws Exception {
+// boolean result = stringJedisClientTem.setNx(key, value, expire, TimeUnit.SECONDS);
+// return result;
+// }
+
+ private String getToken(String guid, String nickname) throws Exception {
+ // 写入用户信息
+ HashMap tokenMap = new HashMap<>();
+ tokenMap.put("userName",nickname);
+ tokenMap.put("id",guid);
+
+ // 生成token
+ //String token = JWTUtil.createToken(tokenMap,tokenKey.getBytes());
+ return StringUtils.getUUID();
+ }
+}
diff --git a/lib/EmergencyService/zdxtEmergencyKnowledgeService/src/main/java/com/zdxt/knowledge/utils/HttpClientUtils.java b/lib/EmergencyService/zdxtEmergencyKnowledgeService/src/main/java/com/zdxt/knowledge/utils/HttpClientUtils.java
new file mode 100644
index 00000000..ae4743c5
--- /dev/null
+++ b/lib/EmergencyService/zdxtEmergencyKnowledgeService/src/main/java/com/zdxt/knowledge/utils/HttpClientUtils.java
@@ -0,0 +1,70 @@
+package com.zdxt.knowledge.utils;
+
+import org.apache.http.NameValuePair;
+import org.apache.http.client.config.RequestConfig;
+import org.apache.http.client.entity.UrlEncodedFormEntity;
+import org.apache.http.client.methods.CloseableHttpResponse;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.HttpClients;
+import org.apache.http.message.BasicNameValuePair;
+import org.apache.http.util.EntityUtils;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+public class HttpClientUtils {
+
+ final static int TIMEOUT = 1000;
+
+ final static int TIMEOUT_MSEC = 5 * 1000;
+
+ public static String doPost(String url, Map paramMap) throws IOException {
+ // 创建Httpclient对象
+ CloseableHttpClient httpClient = HttpClients.createDefault();
+ CloseableHttpResponse response = null;
+ String resultString = "";
+
+ try {
+ // 创建Http Post请求
+ HttpPost httpPost = new HttpPost(url);
+
+ // 创建参数列表
+ if (paramMap != null) {
+ List paramList = new ArrayList<>();
+ for (Map.Entry param : paramMap.entrySet()) {
+ paramList.add(new BasicNameValuePair(param.getKey(), param.getValue()));
+ }
+ // 模拟表单
+ UrlEncodedFormEntity entity = new UrlEncodedFormEntity(paramList);
+ httpPost.setEntity(entity);
+ }
+
+ httpPost.setConfig(builderRequestConfig());
+
+ // 执行http请求
+ response = httpClient.execute(httpPost);
+
+ resultString = EntityUtils.toString(response.getEntity(), "UTF-8");
+ } catch (Exception e) {
+ throw e;
+ } finally {
+ try {
+ response.close();
+ } catch (IOException e) {
+ throw e;
+ }
+ }
+
+ return resultString;
+ }
+
+ private static RequestConfig builderRequestConfig() {
+ return RequestConfig.custom()
+ .setConnectTimeout(TIMEOUT_MSEC)
+ .setConnectionRequestTimeout(TIMEOUT_MSEC)
+ .setSocketTimeout(TIMEOUT_MSEC).build();
+ }
+}
diff --git a/lib/EmergencyService/zdxtEmergencyKnowledgeService/src/main/resources/mapper/SceneMedalMapper.xml b/lib/EmergencyService/zdxtEmergencyKnowledgeService/src/main/resources/mapper/SceneMedalMapper.xml
index 13a32a0e..fa8804a2 100644
--- a/lib/EmergencyService/zdxtEmergencyKnowledgeService/src/main/resources/mapper/SceneMedalMapper.xml
+++ b/lib/EmergencyService/zdxtEmergencyKnowledgeService/src/main/resources/mapper/SceneMedalMapper.xml
@@ -32,7 +32,6 @@
and name like concat('%', #{name}, '%')
and creator = #{creator}
- order by sort ASC